Compare commits

..

1 Commits

Author SHA1 Message Date
Zanie
6c8017ef13 Allow additional text to folllow formatter pragma comments 2023-11-28 13:32:48 -06:00
627 changed files with 3063 additions and 28191 deletions

View File

@@ -23,13 +23,8 @@ jobs:
name: "Determine changes"
runs-on: ubuntu-latest
outputs:
# Flag that is raised when any code that affects linter is changed
linter: ${{ steps.changed.outputs.linter_any_changed }}
# Flag that is raised when any code that affects formatter is changed
formatter: ${{ steps.changed.outputs.formatter_any_changed }}
# Flag that is raised when any code is changed
# This is superset of the linter and formatter
code: ${{ steps.changed.outputs.code_any_changed }}
steps:
- uses: actions/checkout@v4
with:
@@ -67,12 +62,6 @@ jobs:
- python/**
- .github/workflows/ci.yaml
code:
- "*/**"
- "!**/*.md"
- "!docs/**"
- "!assets/**"
cargo-fmt:
name: "cargo fmt"
runs-on: ubuntu-latest
@@ -85,8 +74,6 @@ jobs:
cargo-clippy:
name: "cargo clippy"
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -101,8 +88,6 @@ jobs:
cargo-test-linux:
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
name: "cargo test (linux)"
steps:
- uses: actions/checkout@v4
@@ -127,8 +112,6 @@ jobs:
cargo-test-windows:
runs-on: windows-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
name: "cargo test (windows)"
steps:
- uses: actions/checkout@v4
@@ -146,8 +129,6 @@ jobs:
cargo-test-wasm:
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
name: "cargo test (wasm)"
steps:
- uses: actions/checkout@v4
@@ -167,8 +148,6 @@ jobs:
cargo-fuzz:
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
name: "cargo fuzz"
steps:
- uses: actions/checkout@v4
@@ -186,8 +165,6 @@ jobs:
scripts:
name: "test scripts"
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
@@ -211,7 +188,8 @@ jobs:
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
# Ecosystem check needs linter and/or formatter changes.
if: github.event_name == 'pull_request' && ${{
needs.determine_changes.outputs.code == 'true'
needs.determine_changes.outputs.linter == 'true' ||
needs.determine_changes.outputs.formatter == 'true'
}}
steps:
- uses: actions/checkout@v4
@@ -320,8 +298,6 @@ jobs:
cargo-udeps:
name: "cargo udeps"
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: "Install nightly Rust toolchain"
@@ -441,10 +417,7 @@ jobs:
check-ruff-lsp:
name: "test ruff-lsp"
runs-on: ubuntu-latest
needs:
- cargo-test-linux
- determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
needs: cargo-test-linux
steps:
- uses: extractions/setup-just@v1
env:
@@ -482,8 +455,6 @@ jobs:
benchmarks:
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main'
steps:
- name: "Checkout Branch"
uses: actions/checkout@v4

View File

@@ -3,9 +3,7 @@ use ruff_benchmark::criterion::{
};
use ruff_benchmark::{TestCase, TestFile, TestFileDownloadError};
use ruff_linter::linter::lint_only;
use ruff_linter::rule_selector::PreviewOptions;
use ruff_linter::settings::rule_table::RuleTable;
use ruff_linter::settings::types::PreviewMode;
use ruff_linter::settings::{flags, LinterSettings};
use ruff_linter::source_kind::SourceKind;
use ruff_linter::{registry::Rule, RuleSelector};
@@ -80,21 +78,12 @@ fn benchmark_default_rules(criterion: &mut Criterion) {
benchmark_linter(group, &LinterSettings::default());
}
/// Disables IO based rules because they are a source of flakiness
fn disable_io_rules(rules: &mut RuleTable) {
fn benchmark_all_rules(criterion: &mut Criterion) {
let mut rules: RuleTable = RuleSelector::All.all_rules().collect();
// Disable IO based rules because it is a source of flakiness
rules.disable(Rule::ShebangMissingExecutableFile);
rules.disable(Rule::ShebangNotExecutable);
}
fn benchmark_all_rules(criterion: &mut Criterion) {
let mut rules: RuleTable = RuleSelector::All
.rules(&PreviewOptions {
mode: PreviewMode::Disabled,
require_explicit: false,
})
.collect();
disable_io_rules(&mut rules);
let settings = LinterSettings {
rules,
@@ -105,22 +94,6 @@ fn benchmark_all_rules(criterion: &mut Criterion) {
benchmark_linter(group, &settings);
}
fn benchmark_preview_rules(criterion: &mut Criterion) {
let mut rules: RuleTable = RuleSelector::All.all_rules().collect();
disable_io_rules(&mut rules);
let settings = LinterSettings {
rules,
preview: PreviewMode::Enabled,
..LinterSettings::default()
};
let group = criterion.benchmark_group("linter/all-with-preview-rules");
benchmark_linter(group, &settings);
}
criterion_group!(default_rules, benchmark_default_rules);
criterion_group!(all_rules, benchmark_all_rules);
criterion_group!(preview_rules, benchmark_preview_rules);
criterion_main!(default_rules, all_rules, preview_rules);
criterion_main!(default_rules, all_rules);

View File

@@ -396,43 +396,3 @@ if __name__ == "__main__":
"###);
Ok(())
}
/// Regression test for [#8858](https://github.com/astral-sh/ruff/issues/8858)
#[test]
fn parent_configuration_override() -> Result<()> {
let tempdir = TempDir::new()?;
let root_ruff = tempdir.path().join("ruff.toml");
fs::write(
root_ruff,
r#"
[lint]
select = ["ALL"]
"#,
)?;
let sub_dir = tempdir.path().join("subdirectory");
fs::create_dir(&sub_dir)?;
let subdirectory_ruff = sub_dir.join("ruff.toml");
fs::write(
subdirectory_ruff,
r#"
[lint]
ignore = ["D203", "D212"]
"#,
)?;
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.current_dir(sub_dir)
.arg("check")
.args(STDIN_BASE_OPTIONS)
, @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: No Python files found under the given path(s)
"###);
Ok(())
}

View File

@@ -1,34 +1,30 @@
//! Print the AST for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use ruff_linter::source_kind::SourceKind;
use ruff_python_ast::PySourceType;
use ruff_python_parser::{parse, AsMode};
use ruff_python_parser::{parse, Mode};
#[derive(clap::Args)]
pub(crate) struct Args {
/// Python file for which to generate the AST.
#[arg(required = true)]
file: PathBuf,
/// Run in Jupyter mode i.e., allow line magics.
#[arg(long)]
jupyter: bool,
}
pub(crate) fn main(args: &Args) -> Result<()> {
let source_type = PySourceType::from(&args.file);
let source_kind = SourceKind::from_path(&args.file, source_type)?.ok_or_else(|| {
anyhow::anyhow!(
"Could not determine source kind for file: {}",
args.file.display()
)
})?;
let python_ast = parse(
source_kind.source_code(),
source_type.as_mode(),
&args.file.to_string_lossy(),
)?;
let contents = fs::read_to_string(&args.file)?;
let mode = if args.jupyter {
Mode::Ipython
} else {
Mode::Module
};
let python_ast = parse(&contents, mode, &args.file.to_string_lossy())?;
println!("{python_ast:#?}");
Ok(())
}

View File

@@ -1,30 +1,30 @@
//! Print the token stream for a given Python file.
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use ruff_linter::source_kind::SourceKind;
use ruff_python_ast::PySourceType;
use ruff_python_parser::{lexer, AsMode};
use ruff_python_parser::{lexer, Mode};
#[derive(clap::Args)]
pub(crate) struct Args {
/// Python file for which to generate the AST.
#[arg(required = true)]
file: PathBuf,
/// Run in Jupyter mode i.e., allow line magics (`%`, `!`, `?`, `/`, `,`, `;`).
#[arg(long)]
jupyter: bool,
}
pub(crate) fn main(args: &Args) -> Result<()> {
let source_type = PySourceType::from(&args.file);
let source_kind = SourceKind::from_path(&args.file, source_type)?.ok_or_else(|| {
anyhow::anyhow!(
"Could not determine source kind for file: {}",
args.file.display()
)
})?;
for (tok, range) in lexer::lex(source_kind.source_code(), source_type.as_mode()).flatten() {
let contents = fs::read_to_string(&args.file)?;
let mode = if args.jupyter {
Mode::Ipython
} else {
Mode::Module
};
for (tok, range) in lexer::lex(&contents, mode).flatten() {
println!(
"{start:#?} {tok:#?} {end:#?}",
start = range.start(),

View File

@@ -1,9 +1,23 @@
use super::{Buffer, Format, Formatter};
use crate::FormatResult;
use std::ffi::c_void;
use std::marker::PhantomData;
/// A convenience wrapper for representing a formattable argument.
/// Mono-morphed type to format an object. Used by the [`crate::format`!], [`crate::format_args`!], and
/// [`crate::write`!] macros.
///
/// This struct is similar to a dynamic dispatch (using `dyn Format`) because it stores a pointer to the value.
/// However, it doesn't store the pointer to `dyn Format`'s vtable, instead it statically resolves the function
/// pointer of `Format::format` and stores it in `formatter`.
pub struct Argument<'fmt, Context> {
value: &'fmt dyn Format<Context>,
/// The value to format stored as a raw pointer where `lifetime` stores the value's lifetime.
value: *const c_void,
/// Stores the lifetime of the value. To get the most out of our dear borrow checker.
lifetime: PhantomData<&'fmt ()>,
/// The function pointer to `value`'s `Format::format` method
formatter: fn(*const c_void, &mut Formatter<'_, Context>) -> FormatResult<()>,
}
impl<Context> Clone for Argument<'_, Context> {
@@ -14,19 +28,32 @@ impl<Context> Clone for Argument<'_, Context> {
impl<Context> Copy for Argument<'_, Context> {}
impl<'fmt, Context> Argument<'fmt, Context> {
/// Called by the [ruff_formatter::format_args] macro.
/// Called by the [ruff_formatter::format_args] macro. Creates a mono-morphed value for formatting
/// an object.
#[doc(hidden)]
#[inline]
pub fn new<F: Format<Context>>(value: &'fmt F) -> Self {
Self { value }
#[inline]
fn formatter<F: Format<Context>, Context>(
ptr: *const c_void,
fmt: &mut Formatter<Context>,
) -> FormatResult<()> {
// SAFETY: Safe because the 'fmt lifetime is captured by the 'lifetime' field.
#[allow(unsafe_code)]
F::fmt(unsafe { &*ptr.cast::<F>() }, fmt)
}
Self {
value: (value as *const F).cast::<std::ffi::c_void>(),
lifetime: PhantomData,
formatter: formatter::<F, Context>,
}
}
/// Formats the value stored by this argument using the given formatter.
#[inline]
// Seems to only be triggered on wasm32 and looks like a false positive?
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(super) fn format(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
self.value.fmt(f)
(self.formatter)(self.value, f)
}
}

View File

@@ -2555,17 +2555,17 @@ pub struct BestFitting<'a, Context> {
}
impl<'a, Context> BestFitting<'a, Context> {
/// Creates a new best fitting IR with the given variants.
///
/// Callers are required to ensure that the number of variants given
/// is at least 2.
/// Creates a new best fitting IR with the given variants. The method itself isn't unsafe
/// but it is to discourage people from using it because the printer will panic if
/// the slice doesn't contain at least the least and most expanded variants.
///
/// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro.
///
/// # Panics
///
/// When the slice contains less than two variants.
pub fn from_arguments_unchecked(variants: Arguments<'a, Context>) -> Self {
/// ## Safety
/// The slice must contain at least two variants.
#[allow(unsafe_code)]
pub unsafe fn from_arguments_unchecked(variants: Arguments<'a, Context>) -> Self {
assert!(
variants.0.len() >= 2,
"Requires at least the least expanded and most expanded variants"
@@ -2696,12 +2696,14 @@ impl<Context> Format<Context> for BestFitting<'_, Context> {
buffer.write_element(FormatElement::Tag(EndBestFittingEntry));
}
// OK because the constructor guarantees that there are always at
// least two variants.
let variants = BestFittingVariants::from_vec_unchecked(buffer.into_vec());
let element = FormatElement::BestFitting {
variants,
mode: self.mode,
// SAFETY: The constructor guarantees that there are always at least two variants. It's, therefore,
// safe to call into the unsafe `from_vec_unchecked` function
#[allow(unsafe_code)]
let element = unsafe {
FormatElement::BestFitting {
variants: BestFittingVariants::from_vec_unchecked(buffer.into_vec()),
mode: self.mode,
}
};
f.write_element(element);

View File

@@ -332,14 +332,17 @@ pub enum BestFittingMode {
pub struct BestFittingVariants(Box<[FormatElement]>);
impl BestFittingVariants {
/// Creates a new best fitting IR with the given variants.
///
/// Callers are required to ensure that the number of variants given
/// is at least 2 when using `most_expanded` or `most_flag`.
/// Creates a new best fitting IR with the given variants. The method itself isn't unsafe
/// but it is to discourage people from using it because the printer will panic if
/// the slice doesn't contain at least the least and most expanded variants.
///
/// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro.
///
/// ## Safety
/// The slice must contain at least two variants.
#[doc(hidden)]
pub fn from_vec_unchecked(variants: Vec<FormatElement>) -> Self {
#[allow(unsafe_code)]
pub unsafe fn from_vec_unchecked(variants: Vec<FormatElement>) -> Self {
debug_assert!(
variants
.iter()
@@ -348,23 +351,12 @@ impl BestFittingVariants {
>= 2,
"Requires at least the least expanded and most expanded variants"
);
Self(variants.into_boxed_slice())
}
/// Returns the most expanded variant
///
/// # Panics
///
/// When the number of variants is less than two.
pub fn most_expanded(&self) -> &[FormatElement] {
assert!(
self.as_slice()
.iter()
.filter(|element| matches!(element, FormatElement::Tag(Tag::StartBestFittingEntry)))
.count()
>= 2,
"Requires at least the least expanded and most expanded variants"
);
self.into_iter().last().unwrap()
}
@@ -373,19 +365,7 @@ impl BestFittingVariants {
}
/// Returns the least expanded variant
///
/// # Panics
///
/// When the number of variants is less than two.
pub fn most_flat(&self) -> &[FormatElement] {
assert!(
self.as_slice()
.iter()
.filter(|element| matches!(element, FormatElement::Tag(Tag::StartBestFittingEntry)))
.count()
>= 2,
"Requires at least the least expanded and most expanded variants"
);
self.into_iter().next().unwrap()
}
}

View File

@@ -329,8 +329,10 @@ macro_rules! format {
#[macro_export]
macro_rules! best_fitting {
($least_expanded:expr, $($tail:expr),+ $(,)?) => {{
// OK because the macro syntax requires at least two variants.
$crate::BestFitting::from_arguments_unchecked($crate::format_args!($least_expanded, $($tail),+))
#[allow(unsafe_code)]
unsafe {
$crate::BestFitting::from_arguments_unchecked($crate::format_args!($least_expanded, $($tail),+))
}
}}
}

View File

@@ -30,36 +30,3 @@ def func(x: int):
def func(x: int):
return 1 + 2.5 if x > 0 else 1.5 or "str"
def func(x: int):
if not x:
return None
return {"foo": 1}
def func(x: int):
return {"foo": 1}
def func(x: int):
if not x:
return 1
else:
return True
def func(x: int):
if not x:
return 1
else:
return None
def func(x: int):
if not x:
return 1
elif x > 5:
return "str"
else:
return None

View File

@@ -106,11 +106,3 @@ def func(x: bool | str):
def func(x: int | str):
pass
from typing import override
@override
def func(x: bool):
pass

View File

@@ -36,54 +36,3 @@ field10: (Literal[1] | str) | Literal[2] # Error
# Should emit for union in generic parent type.
field11: dict[Literal[1] | Literal[2], str] # Error
# Should emit for unions with more than two cases
field12: Literal[1] | Literal[2] | Literal[3] # Error
field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
# Should emit for unions with more than two cases, even if not directly adjacent
field14: Literal[1] | Literal[2] | str | Literal[3] # Error
# Should emit for unions with mixed literal internal types
field15: Literal[1] | Literal["foo"] | Literal[True] # Error
# Shouldn't emit for duplicate field types with same value; covered by Y016
field16: Literal[1] | Literal[1] # OK
# Shouldn't emit if in new parent type
field17: Literal[1] | dict[Literal[2], str] # OK
# Shouldn't emit if not in a union parent
field18: dict[Literal[1], Literal[2]] # OK
# Should respect name of literal type used
field19: typing.Literal[1] | typing.Literal[2] # Error
# Should emit in cases with newlines
field20: typing.Union[
Literal[
1 # test
],
Literal[2],
] # Error, newline and comment will not be emitted in message
# Should handle multiple unions with multiple members
field21: Literal[1, 2] | Literal[3, 4] # Error
# Should emit in cases with `typing.Union` instead of `|`
field22: typing.Union[Literal[1], Literal[2]] # Error
# Should emit in cases with `typing_extensions.Literal`
field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
# Should emit in cases with nested `typing.Union`
field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
# Should emit in cases with mixed `typing.Union` and `|`
field25: typing.Union[Literal[1], Literal[2] | str] # Error
# Should emit only once in cases with multiple nested `typing.Union`
field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
# Should use the first literal subscript attribute when fixing
field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error

View File

@@ -84,6 +84,3 @@ field25: typing.Union[Literal[1], Literal[2] | str] # Error
# Should emit only once in cases with multiple nested `typing.Union`
field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
# Should use the first literal subscript attribute when fixing
field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error

View File

@@ -26,10 +26,5 @@ def func():
from trio import Event, sleep
def func():
sleep(0) # TRIO115
async def func():
await sleep(seconds=0) # TRIO115

View File

@@ -1,34 +0,0 @@
"""Test module."""
from __future__ import annotations
from functools import singledispatch
from typing import TYPE_CHECKING
from numpy import asarray
from numpy.typing import ArrayLike
from scipy.sparse import spmatrix
from pandas import DataFrame
if TYPE_CHECKING:
from numpy import ndarray
@singledispatch
def to_array_or_mat(a: ArrayLike | spmatrix) -> ndarray | spmatrix:
"""Convert arg to array or leaves it as sparse matrix."""
msg = f"Unhandled type {type(a)}"
raise NotImplementedError(msg)
@to_array_or_mat.register
def _(a: ArrayLike) -> ndarray:
return asarray(a)
@to_array_or_mat.register
def _(a: spmatrix) -> spmatrix:
return a
def _(a: DataFrame) -> DataFrame:
return a

View File

@@ -1,3 +0,0 @@
from mediuuuuuuuuuuum import a
from short import b
from loooooooooooooooooooooog import c

View File

@@ -1,11 +0,0 @@
from module1 import (
loooooooooooooong,
σηορτ,
mediuuuuum,
shoort,
looooooooooooooong,
μεδιυυυυυμ,
short,
mediuuuuuum,
λοοοοοοοοοοοοοονγ,
)

View File

@@ -1,9 +0,0 @@
import loooooooooooooong
import mediuuuuuum
import short
import σηορτ
import shoort
import mediuuuuum
import λοοοοοοοοοοοοοονγ
import μεδιυυυυυμ
import looooooooooooooong

View File

@@ -1,6 +0,0 @@
import mediuuuuuum
import short
import looooooooooooooooong
from looooooooooooooong import a
from mediuuuum import c
from short import b

View File

@@ -1,4 +0,0 @@
import mediuuuuuumb
import short
import looooooooooooooooong
import mediuuuuuuma

View File

@@ -1,7 +0,0 @@
from ..looooooooooooooong import a
from ...mediuuuum import b
from .short import c
from ....short import c
from . import d
from .mediuuuum import a
from ......short import b

View File

@@ -1,3 +0,0 @@
from looooooooooooooong import a
from mediuuuum import *
from short import *

View File

@@ -1,6 +1,6 @@
import collections
from collections import namedtuple
from typing import Type, TypeAlias, TypeVar, NewType, NamedTuple, TypedDict
from typing import TypeAlias, TypeVar, NewType, NamedTuple, TypedDict
GLOBAL: str = "foo"
@@ -40,15 +40,3 @@ def loop_assign():
global CURRENT_PORT
for CURRENT_PORT in range(5):
pass
def model_assign() -> None:
Bad = apps.get_model("zerver", "Stream") # N806
Attachment = apps.get_model("zerver", "Attachment") # OK
Recipient = apps.get_model("zerver", model_name="Recipient") # OK
Address: Type = apps.get_model("zerver", "Address") # OK
from django.utils.module_loading import import_string
Bad = import_string("django.core.exceptions.ValidationError") # N806
ValidationError = import_string("django.core.exceptions.ValidationError") # OK

View File

@@ -1,113 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "33faf7ad-a3fd-4ac4-a0c3-52e507ed49df",
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"\n",
"sys.path"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1331140f-2741-4661-9086-0764368710c9",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4113383-725d-4f04-80b8-a3080b2b8c4b",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"os.path\n",
"\n",
"import pathlib"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a5d2ef63-ae60-4311-bae3-42e845afba3f",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "79599475-a5ee-4f60-80d1-6efa77693da0",
"metadata": {},
"outputs": [],
"source": [
"import a\n",
"\n",
"try:\n",
" import b\n",
"except ImportError:\n",
" pass\n",
"else:\n",
" pass\n",
"\n",
"__some__magic = 1\n",
"\n",
"import c"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "863dcc35-5c8d-4d05-8b4a-91059e944112",
"metadata": {},
"outputs": [],
"source": [
"import ok\n",
"\n",
"\n",
"def foo() -> None:\n",
" import e\n",
"\n",
"\n",
"import no_ok"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b2377d0-b814-4057-83ec-d443d8e19401",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python (ruff-playground)",
"language": "python",
"name": "ruff-playground"
},
"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
}

View File

@@ -1,5 +0,0 @@
class Platform:
""" Remove sampler
Args:
    Returns:
"""

View File

@@ -91,12 +91,3 @@ def f(rounds: list[int], number: int) -> bool:
bool: was the round played?
"""
return number in rounds
def f():
"""
My example
==========
My example explanation
"""

View File

@@ -1,19 +0,0 @@
from random import choice
class Fruit:
COLORS = []
def __init__(self, color):
self.color = color
def pick_colors(cls, *args): # [no-classmethod-decorator]
"""classmethod to pick fruit colors"""
cls.COLORS = args
pick_colors = classmethod(pick_colors)
def pick_one_color(): # [no-staticmethod-decorator]
"""staticmethod to pick one fruit color"""
return choice(Fruit.COLORS)
pick_one_color = staticmethod(pick_one_color)

View File

@@ -1,59 +0,0 @@
import builtins
letters = ["a", "b", "c"]
def fix_these():
[letters[index] for index, letter in enumerate(letters)] # PLR1736
{letters[index] for index, letter in enumerate(letters)} # PLR1736
{letter: letters[index] for index, letter in enumerate(letters)} # PLR1736
for index, letter in enumerate(letters):
print(letters[index]) # PLR1736
blah = letters[index] # PLR1736
assert letters[index] == "d" # PLR1736
for index, letter in builtins.enumerate(letters):
print(letters[index]) # PLR1736
blah = letters[index] # PLR1736
assert letters[index] == "d" # PLR1736
def dont_fix_these():
# once there is an assignment to the sequence[index], we stop emitting diagnostics
for index, letter in enumerate(letters):
letters[index] = "d" # Ok
letters[index] += "e" # Ok
assert letters[index] == "de" # Ok
# once there is an assignment to the index, we stop emitting diagnostics
for index, letter in enumerate(letters):
index += 1 # Ok
print(letters[index]) # Ok
# once there is an assignment to the sequence, we stop emitting diagnostics
for index, letter in enumerate(letters):
letters = ["d", "e", "f"] # Ok
print(letters[index]) # Ok
# once there is an deletion from or of the sequence or index, we stop emitting diagnostics
for index, letter in enumerate(letters):
del letters[index] # Ok
print(letters[index]) # Ok
for index, letter in enumerate(letters):
del letters # Ok
print(letters[index]) # Ok
for index, letter in enumerate(letters):
del index # Ok
print(letters[index]) # Ok
def value_intentionally_unused():
[letters[index] for index, _ in enumerate(letters)] # Ok
{letters[index] for index, _ in enumerate(letters)} # Ok
{index: letters[index] for index, _ in enumerate(letters)} # Ok
for index, _ in enumerate(letters):
print(letters[index]) # Ok
blah = letters[index] # Ok
letters[index] = "d" # Ok

View File

@@ -1,47 +0,0 @@
import math
from math import e as special_e
from math import log as special_log
# Errors.
math.log(1, 2)
math.log(1, 10)
math.log(1, math.e)
foo = ...
math.log(foo, 2)
math.log(foo, 10)
math.log(foo, math.e)
math.log(1, special_e)
special_log(1, 2)
special_log(1, 10)
special_log(1, math.e)
special_log(1, special_e)
# Ok.
math.log2(1)
math.log10(1)
math.log(1)
math.log(1, 3)
math.log(1, math.pi)
two = 2
math.log(1, two)
ten = 10
math.log(1, ten)
e = math.e
math.log(1, e)
math.log2(1, 10) # math.log2 takes only one argument.
math.log10(1, 2) # math.log10 takes only one argument.
math.log(1, base=2) # math.log does not accept keyword arguments.
def log(*args):
print(f"Logging: {args}")
log(1, 2)
log(1, 10)
log(1, math.e)

View File

@@ -205,9 +205,19 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
ExprContext::Store => {
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
if checker.semantic.current_scope().kind.is_function() {
pep8_naming::rules::non_lowercase_variable_in_function(
checker, expr, id,
);
// Ignore globals.
if !checker
.semantic
.current_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,
);
}
}
}
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
@@ -370,13 +380,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flynt::rules::static_join_to_fstring(
checker,
expr,
string_value.to_str(),
string_value.as_str(),
);
}
} else if attr == "format" {
// "...".format(...) call
let location = expr.range();
match pyflakes::format::FormatSummary::try_from(string_value.to_str()) {
match pyflakes::format::FormatSummary::try_from(string_value.as_str()) {
Err(e) => {
if checker.enabled(Rule::StringDotFormatInvalidFormat) {
checker.diagnostics.push(Diagnostic::new(
@@ -422,7 +432,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::BadStringFormatCharacter) {
pylint::rules::bad_string_format_character::call(
checker,
string_value.to_str(),
string_value.as_str(),
location,
);
}
@@ -918,9 +928,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::PrintEmptyString) {
refurb::rules::print_empty_string(checker, call);
}
if checker.enabled(Rule::RedundantLogBase) {
refurb::rules::redundant_log_base(checker, call);
}
if checker.enabled(Rule::QuadraticListSummation) {
ruff::rules::quadratic_list_summation(checker, call);
}
@@ -1035,7 +1042,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Rule::PercentFormatUnsupportedFormatCharacter,
]) {
let location = expr.range();
match pyflakes::cformat::CFormatSummary::try_from(value.to_str()) {
match pyflakes::cformat::CFormatSummary::try_from(value.as_str()) {
Err(CFormatError {
typ: CFormatErrorType::UnsupportedFormatChar(c),
..
@@ -1330,9 +1337,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
range: _,
},
) => {
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
}
if checker.enabled(Rule::UnnecessaryComprehension) {
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
checker, expr, elt, generators,
@@ -1357,9 +1361,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
range: _,
},
) => {
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
}
if checker.enabled(Rule::UnnecessaryComprehension) {
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
checker, expr, elt, generators,
@@ -1383,9 +1384,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
generators,
range: _,
}) => {
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
}
if checker.enabled(Rule::UnnecessaryComprehension) {
flake8_comprehensions::rules::unnecessary_dict_comprehension(
checker, expr, key, value, generators,
@@ -1410,9 +1408,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
range: _,
},
) => {
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
}
if checker.enabled(Rule::FunctionUsesLoopVariable) {
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
}

View File

@@ -384,12 +384,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
range: _,
},
) => {
if checker.enabled(Rule::NoClassmethodDecorator) {
pylint::rules::no_classmethod_decorator(checker, class_def);
}
if checker.enabled(Rule::NoStaticmethodDecorator) {
pylint::rules::no_staticmethod_decorator(checker, class_def);
}
if checker.enabled(Rule::DjangoNullableModelStringField) {
flake8_django::rules::nullable_model_string_field(checker, body);
}
@@ -1277,9 +1271,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryListCast) {
perflint::rules::unnecessary_list_cast(checker, iter, body);
}
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
pylint::rules::unnecessary_list_index_lookup(checker, for_stmt);
}
if !is_async {
if checker.enabled(Rule::ReimplementedBuiltin) {
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);

View File

@@ -107,8 +107,6 @@ pub(crate) struct Checker<'a> {
pub(crate) diagnostics: Vec<Diagnostic>,
/// The list of names already seen by flake8-bugbear diagnostics, to avoid duplicate violations..
pub(crate) flake8_bugbear_seen: Vec<TextRange>,
/// The end offset of the last visited statement.
last_stmt_end: TextSize,
}
impl<'a> Checker<'a> {
@@ -144,7 +142,6 @@ impl<'a> Checker<'a> {
diagnostics: Vec::default(),
flake8_bugbear_seen: Vec::default(),
cell_offsets,
last_stmt_end: TextSize::default(),
}
}
}
@@ -271,18 +268,6 @@ where
// Step 0: Pre-processing
self.semantic.push_node(stmt);
// For Jupyter Notebooks, we'll reset the `IMPORT_BOUNDARY` flag when
// we encounter a cell boundary.
if self.source_type.is_ipynb()
&& self.semantic.at_top_level()
&& self.semantic.seen_import_boundary()
&& self.cell_offsets.is_some_and(|cell_offsets| {
cell_offsets.has_cell_boundary(TextRange::new(self.last_stmt_end, stmt.start()))
})
{
self.semantic.flags -= SemanticModelFlags::IMPORT_BOUNDARY;
}
// Track whether we've seen docstrings, non-imports, etc.
match stmt {
Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. }) => {
@@ -492,13 +477,6 @@ where
// are enabled.
let runtime_annotation = !self.semantic.future_annotations();
// The first parameter may be a single dispatch.
let mut singledispatch =
flake8_type_checking::helpers::is_singledispatch_implementation(
function_def,
self.semantic(),
);
self.semantic.push_scope(ScopeKind::Type);
if let Some(type_params) = type_params {
@@ -512,7 +490,7 @@ where
.chain(&parameters.kwonlyargs)
{
if let Some(expr) = &parameter_with_default.parameter.annotation {
if runtime_annotation || singledispatch {
if runtime_annotation {
self.visit_runtime_annotation(expr);
} else {
self.visit_annotation(expr);
@@ -521,7 +499,6 @@ where
if let Some(expr) = &parameter_with_default.default {
self.visit_expr(expr);
}
singledispatch = false;
}
if let Some(arg) = &parameters.vararg {
if let Some(expr) = &arg.annotation {
@@ -678,24 +655,23 @@ 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() {
self.semantic
.current_scope()
.kind
.as_class()
.is_some_and(|class_def| {
flake8_type_checking::helpers::runtime_evaluated_class(
class_def,
&self
.settings
.flake8_type_checking
.runtime_evaluated_base_classes,
&self
.settings
.flake8_type_checking
.runtime_evaluated_decorators,
&self.semantic,
)
})
if self.semantic.current_scope().kind.is_class() {
let baseclasses = &self
.settings
.flake8_type_checking
.runtime_evaluated_base_classes;
let decorators = &self
.settings
.flake8_type_checking
.runtime_evaluated_decorators;
flake8_type_checking::helpers::runtime_evaluated(
baseclasses,
decorators,
&self.semantic,
)
} else {
false
}
} else {
matches!(
self.semantic.current_scope().kind,
@@ -803,7 +779,6 @@ where
self.semantic.flags = flags_snapshot;
self.semantic.pop_node();
self.last_stmt_end = stmt.end();
}
fn visit_annotation(&mut self, expr: &'b Expr) {
@@ -824,7 +799,7 @@ where
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
self.deferred.string_type_definitions.push((
expr.range(),
value.to_str(),
value.as_str(),
self.semantic.snapshot(),
));
} else {
@@ -1244,7 +1219,7 @@ where
{
self.deferred.string_type_definitions.push((
expr.range(),
value.to_str(),
value.as_str(),
self.semantic.snapshot(),
));
}

View File

@@ -245,8 +245,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "E2515") => (RuleGroup::Stable, rules::pylint::rules::InvalidCharacterZeroWidthSpace),
(Pylint, "R0124") => (RuleGroup::Stable, rules::pylint::rules::ComparisonWithItself),
(Pylint, "R0133") => (RuleGroup::Stable, rules::pylint::rules::ComparisonOfConstant),
(Pylint, "R0202") => (RuleGroup::Preview, rules::pylint::rules::NoClassmethodDecorator),
(Pylint, "R0203") => (RuleGroup::Preview, rules::pylint::rules::NoStaticmethodDecorator),
(Pylint, "R0206") => (RuleGroup::Stable, rules::pylint::rules::PropertyWithParameters),
(Pylint, "R0402") => (RuleGroup::Stable, rules::pylint::rules::ManualFromImport),
(Pylint, "R0911") => (RuleGroup::Stable, rules::pylint::rules::TooManyReturnStatements),
@@ -260,7 +258,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
(Pylint, "R1706") => (RuleGroup::Preview, rules::pylint::rules::AndOrTernary),
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
(Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison),
(Pylint, "R5501") => (RuleGroup::Stable, rules::pylint::rules::CollapsibleElseIf),
(Pylint, "R6201") => (RuleGroup::Preview, rules::pylint::rules::LiteralMembership),
@@ -958,7 +955,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
(Refurb, "152") => (RuleGroup::Preview, rules::refurb::rules::MathConstant),
(Refurb, "163") => (RuleGroup::Preview, rules::refurb::rules::RedundantLogBase),
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),

View File

@@ -1,17 +1,12 @@
use itertools::Itertools;
use ruff_diagnostics::Edit;
use rustc_hash::FxHashSet;
use crate::importer::{ImportRequest, Importer};
use ruff_python_ast::helpers::{
pep_604_union, typing_optional, typing_union, ReturnStatementVisitor,
};
use ruff_python_ast::helpers::{pep_604_union, ReturnStatementVisitor};
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, Expr, ExprContext};
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::{Definition, SemanticModel};
use ruff_text_size::{TextRange, TextSize};
use ruff_text_size::TextRange;
use crate::settings::types::PythonVersion;
@@ -43,7 +38,10 @@ pub(crate) fn is_overload_impl(
}
/// Given a function, guess its return type.
pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPythonType> {
pub(crate) fn auto_return_type(
function: &ast::StmtFunctionDef,
target_version: PythonVersion,
) -> Option<Expr> {
// Collect all the `return` statements.
let returns = {
let mut visitor = ReturnStatementVisitor::default();
@@ -70,94 +68,24 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPy
}
match return_type {
ResolvedPythonType::Atom(python_type) => Some(AutoPythonType::Atom(python_type)),
ResolvedPythonType::Union(python_types) => Some(AutoPythonType::Union(python_types)),
ResolvedPythonType::Atom(python_type) => type_expr(python_type),
ResolvedPythonType::Union(python_types) if target_version >= PythonVersion::Py310 => {
// Aggregate all the individual types (e.g., `int`, `float`).
let names = python_types
.iter()
.sorted_unstable()
.filter_map(|python_type| type_expr(*python_type))
.collect::<Vec<_>>();
// Wrap in a bitwise union (e.g., `int | float`).
Some(pep_604_union(&names))
}
ResolvedPythonType::Union(_) => None,
ResolvedPythonType::Unknown => None,
ResolvedPythonType::TypeError => None,
}
}
#[derive(Debug)]
pub(crate) enum AutoPythonType {
Atom(PythonType),
Union(FxHashSet<PythonType>),
}
impl AutoPythonType {
/// Convert an [`AutoPythonType`] into an [`Expr`].
///
/// If the [`Expr`] relies on importing any external symbols, those imports will be returned as
/// additional edits.
pub(crate) fn into_expression(
self,
importer: &Importer,
at: TextSize,
semantic: &SemanticModel,
target_version: PythonVersion,
) -> Option<(Expr, Vec<Edit>)> {
match self {
AutoPythonType::Atom(python_type) => {
let expr = type_expr(python_type)?;
Some((expr, vec![]))
}
AutoPythonType::Union(python_types) => {
if target_version >= PythonVersion::Py310 {
// Aggregate all the individual types (e.g., `int`, `float`).
let names = python_types
.iter()
.sorted_unstable()
.map(|python_type| type_expr(*python_type))
.collect::<Option<Vec<_>>>()?;
// Wrap in a bitwise union (e.g., `int | float`).
let expr = pep_604_union(&names);
Some((expr, vec![]))
} else {
let python_types = python_types
.into_iter()
.sorted_unstable()
.collect::<Vec<_>>();
match python_types.as_slice() {
[python_type, PythonType::None] | [PythonType::None, python_type] => {
let element = type_expr(*python_type)?;
// Ex) `Optional[int]`
let (optional_edit, binding) = importer
.get_or_import_symbol(
&ImportRequest::import_from("typing", "Optional"),
at,
semantic,
)
.ok()?;
let expr = typing_optional(element, binding);
Some((expr, vec![optional_edit]))
}
_ => {
let elements = python_types
.into_iter()
.map(type_expr)
.collect::<Option<Vec<_>>>()?;
// Ex) `Union[int, str]`
let (union_edit, binding) = importer
.get_or_import_symbol(
&ImportRequest::import_from("typing", "Union"),
at,
semantic,
)
.ok()?;
let expr = typing_union(&elements, binding);
Some((expr, vec![union_edit]))
}
}
}
}
}
}
}
/// Given a [`PythonType`], return an [`Expr`] that resolves to that type.
fn type_expr(python_type: PythonType) -> Option<Expr> {
fn name(name: &str) -> Expr {

View File

@@ -11,7 +11,6 @@ mod tests {
use crate::assert_messages;
use crate::registry::Rule;
use crate::settings::types::PythonVersion;
use crate::settings::LinterSettings;
use crate::test::test_path;
@@ -129,25 +128,6 @@ mod tests {
Ok(())
}
#[test]
fn auto_return_type_py38() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_annotations/auto_return_type.py"),
&LinterSettings {
target_version: PythonVersion::Py38,
..LinterSettings::for_rules(vec![
Rule::MissingReturnTypeUndocumentedPublicFunction,
Rule::MissingReturnTypePrivateFunction,
Rule::MissingReturnTypeSpecialMethod,
Rule::MissingReturnTypeStaticMethod,
Rule::MissingReturnTypeClassMethod,
])
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn suppress_none_returning() -> Result<()> {
let diagnostics = test_path(

View File

@@ -508,7 +508,7 @@ fn check_dynamically_typed<F>(
if let Expr::StringLiteral(ast::ExprStringLiteral { range, value }) = annotation {
// Quoted annotations
if let Ok((parsed_annotation, _)) =
parse_type_annotation(value.to_str(), *range, checker.locator().contents())
parse_type_annotation(value.as_str(), *range, checker.locator().contents())
{
if type_hint_resolves_to_any(
&parsed_annotation,
@@ -725,55 +725,39 @@ pub(crate) fn definition(
) {
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
let return_type = auto_return_type(function)
.and_then(|return_type| {
return_type.into_expression(
checker.importer(),
function.parameters.start(),
checker.semantic(),
checker.settings.target_version,
)
})
.map(|(return_type, edits)| (checker.generator().expr(&return_type), edits));
let return_type = auto_return_type(function, checker.settings.target_version)
.map(|return_type| checker.generator().expr(&return_type));
let mut diagnostic = Diagnostic::new(
MissingReturnTypeClassMethod {
name: name.to_string(),
annotation: return_type.clone().map(|(return_type, ..)| return_type),
annotation: return_type.clone(),
},
function.identifier(),
);
if let Some((return_type, edits)) = return_type {
diagnostic.set_fix(Fix::unsafe_edits(
Edit::insertion(format!(" -> {return_type}"), function.parameters.end()),
edits,
));
if let Some(return_type) = return_type {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.range().end(),
)));
}
diagnostics.push(diagnostic);
}
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
let return_type = auto_return_type(function)
.and_then(|return_type| {
return_type.into_expression(
checker.importer(),
function.parameters.start(),
checker.semantic(),
checker.settings.target_version,
)
})
.map(|(return_type, edits)| (checker.generator().expr(&return_type), edits));
let return_type = auto_return_type(function, checker.settings.target_version)
.map(|return_type| checker.generator().expr(&return_type));
let mut diagnostic = Diagnostic::new(
MissingReturnTypeStaticMethod {
name: name.to_string(),
annotation: return_type.clone().map(|(return_type, ..)| return_type),
annotation: return_type.clone(),
},
function.identifier(),
);
if let Some((return_type, edits)) = return_type {
diagnostic.set_fix(Fix::unsafe_edits(
Edit::insertion(format!(" -> {return_type}"), function.parameters.end()),
edits,
));
if let Some(return_type) = return_type {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.range().end(),
)));
}
diagnostics.push(diagnostic);
}
@@ -791,7 +775,7 @@ pub(crate) fn definition(
);
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
" -> None".to_string(),
function.parameters.end(),
function.parameters.range().end(),
)));
diagnostics.push(diagnostic);
}
@@ -809,7 +793,7 @@ pub(crate) fn definition(
if let Some(return_type) = return_type {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.end(),
function.parameters.range().end(),
)));
}
diagnostics.push(diagnostic);
@@ -818,70 +802,42 @@ pub(crate) fn definition(
match visibility {
visibility::Visibility::Public => {
if checker.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction) {
let return_type = auto_return_type(function)
.and_then(|return_type| {
return_type.into_expression(
checker.importer(),
function.parameters.start(),
checker.semantic(),
checker.settings.target_version,
)
})
.map(|(return_type, edits)| {
(checker.generator().expr(&return_type), edits)
});
let return_type =
auto_return_type(function, checker.settings.target_version)
.map(|return_type| checker.generator().expr(&return_type));
let mut diagnostic = Diagnostic::new(
MissingReturnTypeUndocumentedPublicFunction {
name: name.to_string(),
annotation: return_type
.clone()
.map(|(return_type, ..)| return_type),
annotation: return_type.clone(),
},
function.identifier(),
);
if let Some((return_type, edits)) = return_type {
diagnostic.set_fix(Fix::unsafe_edits(
Edit::insertion(
format!(" -> {return_type}"),
function.parameters.end(),
),
edits,
));
if let Some(return_type) = return_type {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.range().end(),
)));
}
diagnostics.push(diagnostic);
}
}
visibility::Visibility::Private => {
if checker.enabled(Rule::MissingReturnTypePrivateFunction) {
let return_type = auto_return_type(function)
.and_then(|return_type| {
return_type.into_expression(
checker.importer(),
function.parameters.start(),
checker.semantic(),
checker.settings.target_version,
)
})
.map(|(return_type, edits)| {
(checker.generator().expr(&return_type), edits)
});
let return_type =
auto_return_type(function, checker.settings.target_version)
.map(|return_type| checker.generator().expr(&return_type));
let mut diagnostic = Diagnostic::new(
MissingReturnTypePrivateFunction {
name: name.to_string(),
annotation: return_type
.clone()
.map(|(return_type, ..)| return_type),
annotation: return_type.clone(),
},
function.identifier(),
);
if let Some((return_type, edits)) = return_type {
diagnostic.set_fix(Fix::unsafe_edits(
Edit::insertion(
format!(" -> {return_type}"),
function.parameters.end(),
),
edits,
));
if let Some(return_type) = return_type {
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
format!(" -> {return_type}"),
function.parameters.range().end(),
)));
}
diagnostics.push(diagnostic);
}

View File

@@ -123,81 +123,5 @@ auto_return_type.py:31:5: ANN201 [*] Missing return type annotation for public f
31 |-def func(x: int):
31 |+def func(x: int) -> str | float:
32 32 | return 1 + 2.5 if x > 0 else 1.5 or "str"
33 33 |
34 34 |
auto_return_type.py:35:5: ANN201 Missing return type annotation for public function `func`
|
35 | def func(x: int):
| ^^^^ ANN201
36 | if not x:
37 | return None
|
= help: Add return type annotation
auto_return_type.py:41:5: ANN201 Missing return type annotation for public function `func`
|
41 | def func(x: int):
| ^^^^ ANN201
42 | return {"foo": 1}
|
= help: Add return type annotation
auto_return_type.py:45:5: ANN201 [*] Missing return type annotation for public function `func`
|
45 | def func(x: int):
| ^^^^ ANN201
46 | if not x:
47 | return 1
|
= help: Add return type annotation: `int`
Unsafe fix
42 42 | return {"foo": 1}
43 43 |
44 44 |
45 |-def func(x: int):
45 |+def func(x: int) -> int:
46 46 | if not x:
47 47 | return 1
48 48 | else:
auto_return_type.py:52:5: ANN201 [*] Missing return type annotation for public function `func`
|
52 | def func(x: int):
| ^^^^ ANN201
53 | if not x:
54 | return 1
|
= help: Add return type annotation: `int | None`
Unsafe fix
49 49 | return True
50 50 |
51 51 |
52 |-def func(x: int):
52 |+def func(x: int) -> int | None:
53 53 | if not x:
54 54 | return 1
55 55 | else:
auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public function `func`
|
59 | def func(x: int):
| ^^^^ ANN201
60 | if not x:
61 | return 1
|
= help: Add return type annotation: `str | int | None`
Unsafe fix
56 56 | return None
57 57 |
58 58 |
59 |-def func(x: int):
59 |+def func(x: int) -> str | int | None:
60 60 | if not x:
61 61 | return 1
62 62 | elif x > 5:

View File

@@ -1,223 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
---
auto_return_type.py:1:5: ANN201 [*] Missing return type annotation for public function `func`
|
1 | def func():
| ^^^^ ANN201
2 | return 1
|
= help: Add return type annotation: `int`
Unsafe fix
1 |-def func():
1 |+def func() -> int:
2 2 | return 1
3 3 |
4 4 |
auto_return_type.py:5:5: ANN201 [*] Missing return type annotation for public function `func`
|
5 | def func():
| ^^^^ ANN201
6 | return 1.5
|
= help: Add return type annotation: `float`
Unsafe fix
2 2 | return 1
3 3 |
4 4 |
5 |-def func():
5 |+def func() -> float:
6 6 | return 1.5
7 7 |
8 8 |
auto_return_type.py:9:5: ANN201 [*] Missing return type annotation for public function `func`
|
9 | def func(x: int):
| ^^^^ ANN201
10 | if x > 0:
11 | return 1
|
= help: Add return type annotation: `float`
Unsafe fix
6 6 | return 1.5
7 7 |
8 8 |
9 |-def func(x: int):
9 |+def func(x: int) -> float:
10 10 | if x > 0:
11 11 | return 1
12 12 | else:
auto_return_type.py:16:5: ANN201 [*] Missing return type annotation for public function `func`
|
16 | def func():
| ^^^^ ANN201
17 | return True
|
= help: Add return type annotation: `bool`
Unsafe fix
13 13 | return 1.5
14 14 |
15 15 |
16 |-def func():
16 |+def func() -> bool:
17 17 | return True
18 18 |
19 19 |
auto_return_type.py:20:5: ANN201 [*] Missing return type annotation for public function `func`
|
20 | def func(x: int):
| ^^^^ ANN201
21 | if x > 0:
22 | return None
|
= help: Add return type annotation: `None`
Unsafe fix
17 17 | return True
18 18 |
19 19 |
20 |-def func(x: int):
20 |+def func(x: int) -> None:
21 21 | if x > 0:
22 22 | return None
23 23 | else:
auto_return_type.py:27:5: ANN201 [*] Missing return type annotation for public function `func`
|
27 | def func(x: int):
| ^^^^ ANN201
28 | return 1 or 2.5 if x > 0 else 1.5 or "str"
|
= help: Add return type annotation: `Union[str | float]`
Unsafe fix
1 |+from typing import Union
1 2 | def func():
2 3 | return 1
3 4 |
--------------------------------------------------------------------------------
24 25 | return
25 26 |
26 27 |
27 |-def func(x: int):
28 |+def func(x: int) -> Union[str | float]:
28 29 | return 1 or 2.5 if x > 0 else 1.5 or "str"
29 30 |
30 31 |
auto_return_type.py:31:5: ANN201 [*] Missing return type annotation for public function `func`
|
31 | def func(x: int):
| ^^^^ ANN201
32 | return 1 + 2.5 if x > 0 else 1.5 or "str"
|
= help: Add return type annotation: `Union[str | float]`
Unsafe fix
1 |+from typing import Union
1 2 | def func():
2 3 | return 1
3 4 |
--------------------------------------------------------------------------------
28 29 | return 1 or 2.5 if x > 0 else 1.5 or "str"
29 30 |
30 31 |
31 |-def func(x: int):
32 |+def func(x: int) -> Union[str | float]:
32 33 | return 1 + 2.5 if x > 0 else 1.5 or "str"
33 34 |
34 35 |
auto_return_type.py:35:5: ANN201 Missing return type annotation for public function `func`
|
35 | def func(x: int):
| ^^^^ ANN201
36 | if not x:
37 | return None
|
= help: Add return type annotation
auto_return_type.py:41:5: ANN201 Missing return type annotation for public function `func`
|
41 | def func(x: int):
| ^^^^ ANN201
42 | return {"foo": 1}
|
= help: Add return type annotation
auto_return_type.py:45:5: ANN201 [*] Missing return type annotation for public function `func`
|
45 | def func(x: int):
| ^^^^ ANN201
46 | if not x:
47 | return 1
|
= help: Add return type annotation: `int`
Unsafe fix
42 42 | return {"foo": 1}
43 43 |
44 44 |
45 |-def func(x: int):
45 |+def func(x: int) -> int:
46 46 | if not x:
47 47 | return 1
48 48 | else:
auto_return_type.py:52:5: ANN201 [*] Missing return type annotation for public function `func`
|
52 | def func(x: int):
| ^^^^ ANN201
53 | if not x:
54 | return 1
|
= help: Add return type annotation: `Optional[int]`
Unsafe fix
1 |+from typing import Optional
1 2 | def func():
2 3 | return 1
3 4 |
--------------------------------------------------------------------------------
49 50 | return True
50 51 |
51 52 |
52 |-def func(x: int):
53 |+def func(x: int) -> Optional[int]:
53 54 | if not x:
54 55 | return 1
55 56 | else:
auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public function `func`
|
59 | def func(x: int):
| ^^^^ ANN201
60 | if not x:
61 | return 1
|
= help: Add return type annotation: `Union[str | int | None]`
Unsafe fix
1 |+from typing import Union
1 2 | def func():
2 3 | return 1
3 4 |
--------------------------------------------------------------------------------
56 57 | return None
57 58 |
58 59 |
59 |-def func(x: int):
60 |+def func(x: int) -> Union[str | int | None]:
60 61 | if not x:
61 62 | return 1
62 63 | elif x > 5:

View File

@@ -10,7 +10,7 @@ static PASSWORD_CANDIDATE_REGEX: Lazy<Regex> = Lazy::new(|| {
pub(super) fn string_literal(expr: &Expr) -> Option<&str> {
match expr {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => Some(value.to_str()),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => Some(value.as_str()),
_ => None,
}
}

View File

@@ -35,7 +35,7 @@ impl Violation for HardcodedBindAllInterfaces {
/// S104
pub(crate) fn hardcoded_bind_all_interfaces(string: &ExprStringLiteral) -> Option<Diagnostic> {
if string.value.to_str() == "0.0.0.0" {
if string.value.as_str() == "0.0.0.0" {
Some(Diagnostic::new(HardcodedBindAllInterfaces, string.range))
} else {
None

View File

@@ -55,7 +55,7 @@ fn password_target(target: &Expr) -> Option<&str> {
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
// d["password"] = "s3cr3t"
Expr::Subscript(ast::ExprSubscript { slice, .. }) => match slice.as_ref() {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.to_str(),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.as_str(),
_ => return None,
},
// obj.password = "s3cr3t"

View File

@@ -93,7 +93,7 @@ pub(crate) fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
let Some(string) = left.as_string_literal_expr() else {
return;
};
string.value.to_str().escape_default().to_string()
string.value.as_str().escape_default().to_string()
}
Expr::Call(ast::ExprCall { func, .. }) => {
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
@@ -106,7 +106,7 @@ pub(crate) fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
let Some(string) = value.as_string_literal_expr() else {
return;
};
string.value.to_str().escape_default().to_string()
string.value.as_str().escape_default().to_string()
}
// f"select * from table where val = {val}"
Expr::FString(f_string) => concatenated_f_string(f_string, checker.locator()),

View File

@@ -57,7 +57,7 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: &ast::ExprS
.flake8_bandit
.hardcoded_tmp_directory
.iter()
.any(|prefix| string.value.to_str().starts_with(prefix))
.any(|prefix| string.value.as_str().starts_with(prefix))
{
return;
}

View File

@@ -855,7 +855,7 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
// If the `url` argument is a string literal, allow `http` and `https` schemes.
if call.arguments.args.iter().all(|arg| !arg.is_starred_expr()) && call.arguments.keywords.iter().all(|keyword| keyword.arg.is_some()) {
if let Some(Expr::StringLiteral(ast::ExprStringLiteral { value, .. })) = &call.arguments.find_argument("url", 0) {
let url = value.to_str().trim_start();
let url = value.as_str().trim_start();
if url.starts_with("http://") || url.starts_with("https://") {
return None;
}

View File

@@ -60,7 +60,7 @@ pub(crate) fn tarfile_unsafe_members(checker: &mut Checker, call: &ast::ExprCall
.arguments
.find_keyword("filter")
.and_then(|keyword| keyword.value.as_string_literal_expr())
.is_some_and(|value| matches!(value.value.to_str(), "data" | "tar"))
.is_some_and(|value| matches!(value.value.as_str(), "data" | "tar"))
{
return;
}

View File

@@ -2,7 +2,6 @@ use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path;
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
use ruff_python_semantic::analyze::visibility;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -95,18 +94,23 @@ impl Violation for BooleanDefaultValuePositionalArgument {
}
}
/// FBT002
pub(crate) fn boolean_default_value_positional_argument(
checker: &mut Checker,
name: &str,
decorator_list: &[Decorator],
parameters: &Parameters,
) {
// Allow Boolean defaults in explicitly-allowed functions.
if is_allowed_func_def(name) {
return;
}
if decorator_list.iter().any(|decorator| {
collect_call_path(&decorator.expression)
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
}) {
return;
}
for ParameterWithDefault {
parameter,
default,
@@ -117,20 +121,6 @@ pub(crate) fn boolean_default_value_positional_argument(
.as_ref()
.is_some_and(|default| default.is_boolean_literal_expr())
{
// Allow Boolean defaults in setters.
if decorator_list.iter().any(|decorator| {
collect_call_path(&decorator.expression)
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
}) {
return;
}
// Allow Boolean defaults in `@override` methods, since they're required to adhere to
// the parent signature.
if visibility::is_override(decorator_list, checker.semantic()) {
return;
}
checker.diagnostics.push(Diagnostic::new(
BooleanDefaultValuePositionalArgument,
parameter.name.range(),

View File

@@ -4,7 +4,6 @@ use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path;
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
@@ -110,11 +109,17 @@ pub(crate) fn boolean_type_hint_positional_argument(
decorator_list: &[Decorator],
parameters: &Parameters,
) {
// Allow Boolean type hints in explicitly-allowed functions.
if is_allowed_func_def(name) {
return;
}
if decorator_list.iter().any(|decorator| {
collect_call_path(&decorator.expression)
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
}) {
return;
}
for ParameterWithDefault {
parameter,
default: _,
@@ -133,26 +138,9 @@ pub(crate) fn boolean_type_hint_positional_argument(
continue;
}
}
// Allow Boolean type hints in setters.
if decorator_list.iter().any(|decorator| {
collect_call_path(&decorator.expression)
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
}) {
return;
}
// Allow Boolean defaults in `@override` methods, since they're required to adhere to
// the parent signature.
if visibility::is_override(decorator_list, checker.semantic()) {
return;
}
// If `bool` isn't actually a reference to the `bool` built-in, return.
if !checker.semantic().is_builtin("bool") {
return;
}
checker.diagnostics.push(Diagnostic::new(
BooleanTypeHintPositionalArgument,
parameter.name.range(),

View File

@@ -69,10 +69,10 @@ pub(crate) fn getattr_with_constant(
let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = arg else {
return;
};
if !is_identifier(value.to_str()) {
if !is_identifier(value.as_str()) {
return;
}
if is_mangled_private(value.to_str()) {
if is_mangled_private(value.as_str()) {
return;
}
if !checker.semantic().is_builtin("getattr") {

View File

@@ -83,10 +83,10 @@ pub(crate) fn setattr_with_constant(
let Expr::StringLiteral(ast::ExprStringLiteral { value: name, .. }) = name else {
return;
};
if !is_identifier(name.to_str()) {
if !is_identifier(name.as_str()) {
return;
}
if is_mangled_private(name.to_str()) {
if is_mangled_private(name.as_str()) {
return;
}
if !checker.semantic().is_builtin("setattr") {
@@ -104,7 +104,7 @@ pub(crate) fn setattr_with_constant(
if expr == child.as_ref() {
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
assignment(obj, name.to_str(), value, checker.generator()),
assignment(obj, name.as_str(), value, checker.generator()),
expr.range(),
)));
checker.diagnostics.push(diagnostic);

View File

@@ -30,14 +30,6 @@ use crate::rules::flake8_comprehensions::fixes;
/// ```python
/// sorted(iterable, reverse=True)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as `reversed` and `reverse=True` will
/// yield different results in the event of custom sort keys or equality
/// functions. Specifically, `reversed` will reverse the order of the
/// collection, while `sorted` with `reverse=True` will perform a stable
/// reverse sort, which will preserve the order of elements that compare as
/// equal.
#[violation]
pub struct UnnecessaryCallAroundSorted {
func: String,

View File

@@ -32,10 +32,6 @@ use crate::rules::flake8_comprehensions::settings::Settings;
/// []
/// ()
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryCollectionCall {
obj_type: String,

View File

@@ -29,11 +29,6 @@ use crate::rules::flake8_comprehensions::fixes;
/// list(iterable)
/// set(iterable)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the comprehension. In most cases, though, comments will be
/// preserved.
#[violation]
pub struct UnnecessaryComprehension {
obj_type: String,

View File

@@ -40,11 +40,6 @@ use crate::rules::flake8_comprehensions::fixes;
/// any(x.id for x in bar)
/// all(x.id for x in bar)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the comprehension. In most cases, though, comments will be
/// preserved.
#[violation]
pub struct UnnecessaryComprehensionAnyAll;

View File

@@ -43,10 +43,6 @@ use crate::rules::flake8_comprehensions::fixes;
/// - Instead of `sorted(tuple(iterable))`, use `sorted(iterable)`.
/// - Instead of `sorted(sorted(iterable))`, use `sorted(iterable)`.
/// - Instead of `sorted(reversed(iterable))`, use `sorted(iterable)`.
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryDoubleCastOrProcess {
inner: String,

View File

@@ -27,10 +27,6 @@ use super::helpers;
/// ```python
/// {x: f(x) for x in foo}
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryGeneratorDict;

View File

@@ -28,10 +28,6 @@ use super::helpers;
/// ```python
/// [f(x) for x in foo]
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryGeneratorList;

View File

@@ -28,10 +28,6 @@ use super::helpers;
/// ```python
/// {f(x) for x in foo}
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryGeneratorSet;

View File

@@ -25,10 +25,6 @@ use super::helpers;
/// ```python
/// [f(x) for x in foo]
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryListCall;

View File

@@ -25,10 +25,6 @@ use super::helpers;
/// ```python
/// {x: f(x) for x in foo}
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryListComprehensionDict;

View File

@@ -26,10 +26,6 @@ use super::helpers;
/// ```python
/// {f(x) for x in foo}
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryListComprehensionSet;

View File

@@ -29,10 +29,6 @@ use super::helpers;
/// {1: 2, 3: 4}
/// {}
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryLiteralDict {
obj_type: String,

View File

@@ -31,10 +31,6 @@ use super::helpers;
/// {1, 2}
/// set()
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryLiteralSet {
obj_type: String,

View File

@@ -46,10 +46,6 @@ impl fmt::Display for DictKind {
/// {}
/// {"a": 1}
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryLiteralWithinDictCall {
kind: DictKind,

View File

@@ -32,10 +32,6 @@ use super::helpers;
/// [1, 2]
/// [1, 2]
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryLiteralWithinListCall {
literal: String,

View File

@@ -33,10 +33,6 @@ use super::helpers;
/// (1, 2)
/// (1, 2)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryLiteralWithinTupleCall {
literal: String,

View File

@@ -22,16 +22,6 @@ use super::helpers;
/// using a generator expression or a comprehension, as the latter approach
/// avoids the function call overhead, in addition to being more readable.
///
/// This rule also applies to `map` calls within `list`, `set`, and `dict`
/// calls. For example:
///
/// - Instead of `list(map(lambda num: num * 2, nums))`, use
/// `[num * 2 for num in nums]`.
/// - Instead of `set(map(lambda num: num % 2 == 0, nums))`, use
/// `{num % 2 == 0 for num in nums}`.
/// - Instead of `dict(map(lambda v: (v, v ** 2), values))`, use
/// `{v: v ** 2 for v in values}`.
///
/// ## Examples
/// ```python
/// map(lambda x: x + 1, iterable)
@@ -42,9 +32,15 @@ use super::helpers;
/// (x + 1 for x in iterable)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
/// This rule also applies to `map` calls within `list`, `set`, and `dict`
/// calls. For example:
///
/// - Instead of `list(map(lambda num: num * 2, nums))`, use
/// `[num * 2 for num in nums]`.
/// - Instead of `set(map(lambda num: num % 2 == 0, nums))`, use
/// `{num % 2 == 0 for num in nums}`.
/// - Instead of `dict(map(lambda v: (v, v ** 2), values))`, use
/// `{v: v ** 2 for v in values}`.
#[violation]
pub struct UnnecessaryMap {
object_type: ObjectType,

View File

@@ -78,7 +78,7 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
if let Some(Expr::StringLiteral(ast::ExprStringLiteral { value: format, .. })) =
call.arguments.args.get(1).as_ref()
{
if format.to_str().contains("%z") {
if format.as_str().contains("%z") {
return;
}
};

View File

@@ -93,7 +93,7 @@ fn check_log_record_attr_clash(checker: &mut Checker, extra: &Keyword) {
for key in keys {
if let Some(key) = &key {
if let Expr::StringLiteral(ast::ExprStringLiteral { value: attr, .. }) = key {
if is_reserved_attr(attr.to_str()) {
if is_reserved_attr(attr.as_str()) {
checker.diagnostics.push(Diagnostic::new(
LoggingExtraAttrClash(attr.to_string()),
key.range(),

View File

@@ -110,8 +110,8 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs
/// Return `Some` if a key is a valid keyword argument name, or `None` otherwise.
fn as_kwarg(key: &Expr) -> Option<&str> {
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = key {
if is_identifier(value.to_str()) {
return Some(value.to_str());
if is_identifier(value.as_str()) {
return Some(value.as_str());
}
}
None

View File

@@ -1,11 +1,9 @@
use ast::{ExprSubscript, Operator};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::{Ranged, TextRange};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::flake8_pyi::helpers::traverse_union;
/// ## What it does
@@ -34,8 +32,6 @@ pub struct UnnecessaryLiteralUnion {
}
impl Violation for UnnecessaryLiteralUnion {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!(
@@ -43,153 +39,36 @@ impl Violation for UnnecessaryLiteralUnion {
self.members.join(", ")
)
}
fn fix_title(&self) -> Option<String> {
Some(format!("Replace with a single `Literal`",))
}
}
fn concatenate_bin_ors(exprs: Vec<&Expr>) -> Expr {
let mut exprs = exprs.into_iter();
let first = exprs.next().unwrap();
exprs.fold((*first).clone(), |acc, expr| {
Expr::BinOp(ast::ExprBinOp {
left: Box::new(acc),
op: Operator::BitOr,
right: Box::new((*expr).clone()),
range: TextRange::default(),
})
})
}
fn make_union(subscript: &ExprSubscript, exprs: Vec<&Expr>) -> Expr {
Expr::Subscript(ast::ExprSubscript {
value: subscript.value.clone(),
slice: Box::new(Expr::Tuple(ast::ExprTuple {
elts: exprs.into_iter().map(|expr| (*expr).clone()).collect(),
range: TextRange::default(),
ctx: ast::ExprContext::Load,
})),
range: TextRange::default(),
ctx: ast::ExprContext::Load,
})
}
fn make_literal_expr(subscript: Option<Expr>, exprs: Vec<&Expr>) -> Expr {
let use_subscript = if let subscript @ Some(_) = subscript {
subscript.unwrap().clone()
} else {
Expr::Name(ast::ExprName {
id: "Literal".to_string(),
range: TextRange::default(),
ctx: ast::ExprContext::Load,
})
};
Expr::Subscript(ast::ExprSubscript {
value: Box::new(use_subscript),
slice: Box::new(Expr::Tuple(ast::ExprTuple {
elts: exprs.into_iter().map(|expr| (*expr).clone()).collect(),
range: TextRange::default(),
ctx: ast::ExprContext::Load,
})),
range: TextRange::default(),
ctx: ast::ExprContext::Load,
})
}
/// PYI030
pub(crate) fn unnecessary_literal_union<'a>(checker: &mut Checker, expr: &'a Expr) {
let mut literal_exprs = Vec::new();
let mut other_exprs = Vec::new();
// for the sake of consistency and correctness, we'll use the first `Literal` subscript attribute
// to construct the fix
let mut literal_subscript = None;
let mut total_literals = 0;
// Split members into `literal_exprs` if they are a `Literal` annotation and `other_exprs` otherwise
// Adds a member to `literal_exprs` if it is a `Literal` annotation
let mut collect_literal_expr = |expr: &'a Expr, _| {
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if checker.semantic().match_typing_expr(value, "Literal") {
total_literals += 1;
if literal_subscript.is_none() {
literal_subscript = Some(*value.clone());
}
// flatten already-unioned literals to later union again
if let Expr::Tuple(ast::ExprTuple {
elts,
range: _,
ctx: _,
}) = slice.as_ref()
{
for expr in elts {
literal_exprs.push(expr);
}
} else {
literal_exprs.push(slice.as_ref());
}
literal_exprs.push(slice);
}
} else {
other_exprs.push(expr);
}
};
// Traverse the union, collect all members, split out the literals from the rest.
// Traverse the union, collect all literal members
traverse_union(&mut collect_literal_expr, checker.semantic(), expr, None);
let union_subscript = expr.as_subscript_expr();
if union_subscript.is_some_and(|subscript| {
!checker
.semantic()
.match_typing_expr(&subscript.value, "Union")
}) {
return;
}
// Raise a violation if more than one.
if total_literals > 1 {
let literal_members: Vec<String> = literal_exprs
.clone()
.into_iter()
.map(|expr| checker.locator().slice(expr).to_string())
.collect();
let mut diagnostic = Diagnostic::new(
// Raise a violation if more than one
if literal_exprs.len() > 1 {
let diagnostic = Diagnostic::new(
UnnecessaryLiteralUnion {
members: literal_members.clone(),
members: literal_exprs
.into_iter()
.map(|expr| checker.locator().slice(expr.as_ref()).to_string())
.collect(),
},
expr.range(),
);
if checker.settings.preview.is_enabled() {
let literals =
make_literal_expr(literal_subscript, literal_exprs.into_iter().collect());
if other_exprs.is_empty() {
// if the union is only literals, we just replace the whole thing with a single literal
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&literals),
expr.range(),
)));
} else {
let mut expr_vec: Vec<&Expr> = other_exprs.clone().into_iter().collect();
expr_vec.insert(0, &literals);
let content = if let Some(subscript) = union_subscript {
checker.generator().expr(&make_union(subscript, expr_vec))
} else {
checker.generator().expr(&concatenate_bin_ors(expr_vec))
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
content,
expr.range(),
)));
}
}
checker.diagnostics.push(diagnostic);
}
}

View File

@@ -127,7 +127,7 @@ pub(crate) fn unrecognized_platform(checker: &mut Checker, test: &Expr) {
// Other values are possible but we don't need them right now.
// This protects against typos.
if checker.enabled(Rule::UnrecognizedPlatformName) {
if !matches!(value.to_str(), "linux" | "win32" | "cygwin" | "darwin") {
if !matches!(value.as_str(), "linux" | "win32" | "cygwin" | "darwin") {
checker.diagnostics.push(Diagnostic::new(
UnrecognizedPlatformName {
platform: value.to_string(),

View File

@@ -9,7 +9,6 @@ PYI030.py:9:9: PYI030 Multiple literal members in a union. Use a single literal,
10 |
11 | # Should emit for union types in arguments.
|
= help: Replace with a single `Literal`
PYI030.py:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -18,7 +17,6 @@ PYI030.py:12:17: PYI030 Multiple literal members in a union. Use a single litera
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
13 | print(arg1)
|
= help: Replace with a single `Literal`
PYI030.py:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -27,7 +25,6 @@ PYI030.py:17:16: PYI030 Multiple literal members in a union. Use a single litera
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
18 | return "my Literal[1]ing"
|
= help: Replace with a single `Literal`
PYI030.py:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -37,7 +34,6 @@ PYI030.py:22:9: PYI030 Multiple literal members in a union. Use a single literal
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.py:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -48,7 +44,6 @@ PYI030.py:23:9: PYI030 Multiple literal members in a union. Use a single literal
24 | field5: Literal[1] | str | Literal[2] # Error
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.py:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -58,7 +53,6 @@ PYI030.py:24:9: PYI030 Multiple literal members in a union. Use a single literal
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.py:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -69,7 +63,6 @@ PYI030.py:25:9: PYI030 Multiple literal members in a union. Use a single literal
26 |
27 | # Should emit for non-type unions.
|
= help: Replace with a single `Literal`
PYI030.py:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -79,7 +72,6 @@ PYI030.py:28:10: PYI030 Multiple literal members in a union. Use a single litera
29 |
30 | # Should emit for parenthesized unions.
|
= help: Replace with a single `Literal`
PYI030.py:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -89,7 +81,6 @@ PYI030.py:31:9: PYI030 Multiple literal members in a union. Use a single literal
32 |
33 | # Should handle user parentheses when fixing.
|
= help: Replace with a single `Literal`
PYI030.py:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -98,7 +89,6 @@ PYI030.py:34:9: PYI030 Multiple literal members in a union. Use a single literal
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
35 | field10: (Literal[1] | str) | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.py:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -109,160 +99,12 @@ PYI030.py:35:10: PYI030 Multiple literal members in a union. Use a single litera
36 |
37 | # Should emit for union in generic parent type.
|
= help: Replace with a single `Literal`
PYI030.py:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
37 | # Should emit for union in generic parent type.
38 | field11: dict[Literal[1] | Literal[2], str] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
39 |
40 | # Should emit for unions with more than two cases
|
= help: Replace with a single `Literal`
PYI030.py:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
|
= help: Replace with a single `Literal`
PYI030.py:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
40 | # Should emit for unions with more than two cases
41 | field12: Literal[1] | Literal[2] | Literal[3] # Error
42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
43 |
44 | # Should emit for unions with more than two cases, even if not directly adjacent
|
= help: Replace with a single `Literal`
PYI030.py:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
44 | # Should emit for unions with more than two cases, even if not directly adjacent
45 | field14: Literal[1] | Literal[2] | str | Literal[3] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
46 |
47 | # Should emit for unions with mixed literal internal types
|
= help: Replace with a single `Literal`
PYI030.py:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
|
47 | # Should emit for unions with mixed literal internal types
48 | field15: Literal[1] | Literal["foo"] | Literal[True] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
49 |
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
|
= help: Replace with a single `Literal`
PYI030.py:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
|
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
51 | field16: Literal[1] | Literal[1] # OK
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
52 |
53 | # Shouldn't emit if in new parent type
|
= help: Replace with a single `Literal`
PYI030.py:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
59 | # Should respect name of literal type used
60 | field19: typing.Literal[1] | typing.Literal[2] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
61 |
62 | # Should emit in cases with newlines
|
= help: Replace with a single `Literal`
PYI030.py:63:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
62 | # Should emit in cases with newlines
63 | field20: typing.Union[
| __________^
64 | | Literal[
65 | | 1 # test
66 | | ],
67 | | Literal[2],
68 | | ] # Error, newline and comment will not be emitted in message
| |_^ PYI030
69 |
70 | # Should handle multiple unions with multiple members
|
= help: Replace with a single `Literal`
PYI030.py:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
70 | # Should handle multiple unions with multiple members
71 | field21: Literal[1, 2] | Literal[3, 4] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
72 |
73 | # Should emit in cases with `typing.Union` instead of `|`
|
= help: Replace with a single `Literal`
PYI030.py:74:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
73 | # Should emit in cases with `typing.Union` instead of `|`
74 | field22: typing.Union[Literal[1], Literal[2]] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
75 |
76 | # Should emit in cases with `typing_extensions.Literal`
|
= help: Replace with a single `Literal`
PYI030.py:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
76 | # Should emit in cases with `typing_extensions.Literal`
77 | field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
78 |
79 | # Should emit in cases with nested `typing.Union`
|
= help: Replace with a single `Literal`
PYI030.py:80:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
79 | # Should emit in cases with nested `typing.Union`
80 | field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
81 |
82 | # Should emit in cases with mixed `typing.Union` and `|`
|
= help: Replace with a single `Literal`
PYI030.py:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
82 | # Should emit in cases with mixed `typing.Union` and `|`
83 | field25: typing.Union[Literal[1], Literal[2] | str] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
84 |
85 | # Should emit only once in cases with multiple nested `typing.Union`
|
= help: Replace with a single `Literal`
PYI030.py:86:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
85 | # Should emit only once in cases with multiple nested `typing.Union`
86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
87 |
88 | # Should use the first literal subscript attribute when fixing
|
= help: Replace with a single `Literal`
PYI030.py:89:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
88 | # Should use the first literal subscript attribute when fixing
89 | field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
= help: Replace with a single `Literal`

View File

@@ -9,7 +9,6 @@ PYI030.pyi:9:9: PYI030 Multiple literal members in a union. Use a single literal
10 |
11 | # Should emit for union types in arguments.
|
= help: Replace with a single `Literal`
PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -18,7 +17,6 @@ PYI030.pyi:12:17: PYI030 Multiple literal members in a union. Use a single liter
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
13 | print(arg1)
|
= help: Replace with a single `Literal`
PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -27,7 +25,6 @@ PYI030.pyi:17:16: PYI030 Multiple literal members in a union. Use a single liter
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI030
18 | return "my Literal[1]ing"
|
= help: Replace with a single `Literal`
PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -37,7 +34,6 @@ PYI030.pyi:22:9: PYI030 Multiple literal members in a union. Use a single litera
23 | field4: str | Literal[1] | Literal[2] # Error
24 | field5: Literal[1] | str | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -48,7 +44,6 @@ PYI030.pyi:23:9: PYI030 Multiple literal members in a union. Use a single litera
24 | field5: Literal[1] | str | Literal[2] # Error
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -58,7 +53,6 @@ PYI030.pyi:24:9: PYI030 Multiple literal members in a union. Use a single litera
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
25 | field6: Literal[1] | bool | Literal[2] | str # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -69,7 +63,6 @@ PYI030.pyi:25:9: PYI030 Multiple literal members in a union. Use a single litera
26 |
27 | # Should emit for non-type unions.
|
= help: Replace with a single `Literal`
PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -79,7 +72,6 @@ PYI030.pyi:28:10: PYI030 Multiple literal members in a union. Use a single liter
29 |
30 | # Should emit for parenthesized unions.
|
= help: Replace with a single `Literal`
PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -89,7 +81,6 @@ PYI030.pyi:31:9: PYI030 Multiple literal members in a union. Use a single litera
32 |
33 | # Should handle user parentheses when fixing.
|
= help: Replace with a single `Literal`
PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -98,7 +89,6 @@ PYI030.pyi:34:9: PYI030 Multiple literal members in a union. Use a single litera
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
35 | field10: (Literal[1] | str) | Literal[2] # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -109,7 +99,6 @@ PYI030.pyi:35:10: PYI030 Multiple literal members in a union. Use a single liter
36 |
37 | # Should emit for union in generic parent type.
|
= help: Replace with a single `Literal`
PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -119,7 +108,6 @@ PYI030.pyi:38:15: PYI030 Multiple literal members in a union. Use a single liter
39 |
40 | # Should emit for unions with more than two cases
|
= help: Replace with a single `Literal`
PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
@@ -128,7 +116,6 @@ PYI030.pyi:41:10: PYI030 Multiple literal members in a union. Use a single liter
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
42 | field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
|
= help: Replace with a single `Literal`
PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
@@ -139,7 +126,6 @@ PYI030.pyi:42:10: PYI030 Multiple literal members in a union. Use a single liter
43 |
44 | # Should emit for unions with more than two cases, even if not directly adjacent
|
= help: Replace with a single `Literal`
PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3]`
|
@@ -149,7 +135,6 @@ PYI030.pyi:45:10: PYI030 Multiple literal members in a union. Use a single liter
46 |
47 | # Should emit for unions with mixed literal internal types
|
= help: Replace with a single `Literal`
PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, "foo", True]`
|
@@ -159,7 +144,6 @@ PYI030.pyi:48:10: PYI030 Multiple literal members in a union. Use a single liter
49 |
50 | # Shouldn't emit for duplicate field types with same value; covered by Y016
|
= help: Replace with a single `Literal`
PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 1]`
|
@@ -169,7 +153,6 @@ PYI030.pyi:51:10: PYI030 Multiple literal members in a union. Use a single liter
52 |
53 | # Shouldn't emit if in new parent type
|
= help: Replace with a single `Literal`
PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -179,7 +162,6 @@ PYI030.pyi:60:10: PYI030 Multiple literal members in a union. Use a single liter
61 |
62 | # Should emit in cases with newlines
|
= help: Replace with a single `Literal`
PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -195,7 +177,6 @@ PYI030.pyi:63:10: PYI030 Multiple literal members in a union. Use a single liter
69 |
70 | # Should handle multiple unions with multiple members
|
= help: Replace with a single `Literal`
PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
@@ -205,7 +186,6 @@ PYI030.pyi:71:10: PYI030 Multiple literal members in a union. Use a single liter
72 |
73 | # Should emit in cases with `typing.Union` instead of `|`
|
= help: Replace with a single `Literal`
PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -215,7 +195,6 @@ PYI030.pyi:74:10: PYI030 Multiple literal members in a union. Use a single liter
75 |
76 | # Should emit in cases with `typing_extensions.Literal`
|
= help: Replace with a single `Literal`
PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -225,7 +204,6 @@ PYI030.pyi:77:10: PYI030 Multiple literal members in a union. Use a single liter
78 |
79 | # Should emit in cases with nested `typing.Union`
|
= help: Replace with a single `Literal`
PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -235,7 +213,6 @@ PYI030.pyi:80:10: PYI030 Multiple literal members in a union. Use a single liter
81 |
82 | # Should emit in cases with mixed `typing.Union` and `|`
|
= help: Replace with a single `Literal`
PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2]`
|
@@ -245,24 +222,12 @@ PYI030.pyi:83:10: PYI030 Multiple literal members in a union. Use a single liter
84 |
85 | # Should emit only once in cases with multiple nested `typing.Union`
|
= help: Replace with a single `Literal`
PYI030.pyi:86:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
85 | # Should emit only once in cases with multiple nested `typing.Union`
86 | field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
87 |
88 | # Should use the first literal subscript attribute when fixing
|
= help: Replace with a single `Literal`
PYI030.pyi:89:10: PYI030 Multiple literal members in a union. Use a single literal, e.g. `Literal[1, 2, 3, 4]`
|
88 | # Should use the first literal subscript attribute when fixing
89 | field25: typing.Union[typing_extensions.Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]], str] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI030
|
= help: Replace with a single `Literal`

View File

@@ -258,7 +258,7 @@ fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> {
if !acc.is_empty() {
acc.push(',');
}
acc.push_str(value.to_str());
acc.push_str(value.as_str());
}
acc
}),
@@ -301,7 +301,7 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
match expr {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
let names = split_names(value.to_str());
let names = split_names(value.as_str());
if names.len() > 1 {
match names_type {
types::ParametrizeNameType::Tuple => {
@@ -476,7 +476,7 @@ fn check_values(checker: &mut Checker, names: &Expr, values: &Expr) {
.parametrize_values_row_type;
let is_multi_named = if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &names {
split_names(value.to_str()).len() > 1
split_names(value.as_str()).len() > 1
} else {
true
};
@@ -550,7 +550,7 @@ fn check_duplicates(checker: &mut Checker, values: &Expr) {
element.range(),
);
if let Some(prev) = prev {
let values_end = values.end() - TextSize::new(1);
let values_end = values.range().end() - TextSize::new(1);
let previous_end =
trailing_comma(prev, checker.locator().contents()).unwrap_or(values_end);
let element_end =

View File

@@ -161,12 +161,12 @@ pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Ex
return;
}
if is_lowercase_allowed(env_var.to_str()) {
if is_lowercase_allowed(env_var.as_str()) {
return;
}
let capital_env_var = env_var.to_str().to_ascii_uppercase();
if capital_env_var == env_var.to_str() {
let capital_env_var = env_var.as_str().to_ascii_uppercase();
if capital_env_var == env_var.as_str() {
return;
}
@@ -201,12 +201,12 @@ fn check_os_environ_subscript(checker: &mut Checker, expr: &Expr) {
return;
};
if is_lowercase_allowed(env_var.to_str()) {
if is_lowercase_allowed(env_var.as_str()) {
return;
}
let capital_env_var = env_var.to_str().to_ascii_uppercase();
if capital_env_var == env_var.to_str() {
let capital_env_var = env_var.as_str().to_ascii_uppercase();
if capital_env_var == env_var.as_str() {
return;
}

View File

@@ -16,18 +16,12 @@ use crate::importer::ImportRequest;
///
/// ## Example
/// ```python
/// import trio
///
///
/// async def func():
/// await trio.sleep(0)
/// ```
///
/// Use instead:
/// ```python
/// import trio
///
///
/// async def func():
/// await trio.lowlevel.checkpoint()
/// ```
@@ -109,7 +103,7 @@ pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
)?;
let reference_edit =
Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range());
let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range());
let arg_edit = Edit::range_deletion(call.arguments.range);
Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit]))
});
checker.diagnostics.push(diagnostic);

View File

@@ -17,7 +17,7 @@ TRIO115.py:5:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s
3 3 | from trio import sleep
4 4 |
5 |- await trio.sleep(0) # TRIO115
5 |+ await trio.lowlevel.checkpoint() # TRIO115
5 |+ await trio.lowlevel.checkpoint # TRIO115
6 6 | await trio.sleep(1) # OK
7 7 | await trio.sleep(0, 1) # OK
8 8 | await trio.sleep(...) # OK
@@ -38,7 +38,7 @@ TRIO115.py:11:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s
9 9 | await trio.sleep() # OK
10 10 |
11 |- trio.sleep(0) # TRIO115
11 |+ trio.lowlevel.checkpoint() # TRIO115
11 |+ trio.lowlevel.checkpoint # TRIO115
12 12 | foo = 0
13 13 | trio.sleep(foo) # TRIO115
14 14 | trio.sleep(1) # OK
@@ -59,7 +59,7 @@ TRIO115.py:13:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s
11 11 | trio.sleep(0) # TRIO115
12 12 | foo = 0
13 |- trio.sleep(foo) # TRIO115
13 |+ trio.lowlevel.checkpoint() # TRIO115
13 |+ trio.lowlevel.checkpoint # TRIO115
14 14 | trio.sleep(1) # OK
15 15 | time.sleep(0) # OK
16 16 |
@@ -80,15 +80,15 @@ TRIO115.py:17:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s
15 15 | time.sleep(0) # OK
16 16 |
17 |- sleep(0) # TRIO115
17 |+ trio.lowlevel.checkpoint() # TRIO115
17 |+ trio.lowlevel.checkpoint # TRIO115
18 18 |
19 19 | bar = "bar"
20 20 | trio.sleep(bar)
TRIO115.py:31:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
TRIO115.py:30:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
30 | def func():
31 | sleep(0) # TRIO115
29 | def func():
30 | sleep(0) # TRIO115
| ^^^^^^^^ TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
@@ -100,36 +100,8 @@ TRIO115.py:31:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.s
27 |-from trio import Event, sleep
27 |+from trio import Event, sleep, lowlevel
28 28 |
29 29 |
30 30 | def func():
31 |- sleep(0) # TRIO115
31 |+ lowlevel.checkpoint() # TRIO115
32 32 |
33 33 |
34 34 | async def func():
TRIO115.py:35:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
34 | async def func():
35 | await sleep(seconds=0) # TRIO115
| ^^^^^^^^^^^^^^^^ TRIO115
|
= help: Replace with `trio.lowlevel.checkpoint()`
Safe fix
24 24 | trio.run(trio.sleep(0)) # TRIO115
25 25 |
26 26 |
27 |-from trio import Event, sleep
27 |+from trio import Event, sleep, lowlevel
28 28 |
29 29 |
30 30 | def func():
--------------------------------------------------------------------------------
32 32 |
33 33 |
34 34 | async def func():
35 |- await sleep(seconds=0) # TRIO115
35 |+ await lowlevel.checkpoint() # TRIO115
29 29 | def func():
30 |- sleep(0) # TRIO115
30 |+ lowlevel.checkpoint # TRIO115

View File

@@ -1,7 +1,7 @@
use ruff_python_ast::call_path::from_qualified_name;
use ruff_python_ast::helpers::{map_callable, map_subscript};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::{Binding, BindingId, BindingKind, SemanticModel};
use ruff_python_ast::{self as ast};
use ruff_python_semantic::{Binding, BindingId, BindingKind, ScopeKind, SemanticModel};
use rustc_hash::FxHashSet;
pub(crate) fn is_valid_runtime_import(binding: &Binding, semantic: &SemanticModel) -> bool {
@@ -18,26 +18,25 @@ pub(crate) fn is_valid_runtime_import(binding: &Binding, semantic: &SemanticMode
}
}
pub(crate) fn runtime_evaluated_class(
class_def: &ast::StmtClassDef,
pub(crate) fn runtime_evaluated(
base_classes: &[String],
decorators: &[String],
semantic: &SemanticModel,
) -> bool {
if runtime_evaluated_base_class(class_def, base_classes, semantic) {
return true;
if !base_classes.is_empty() {
if runtime_evaluated_base_class(base_classes, semantic) {
return true;
}
}
if runtime_evaluated_decorators(class_def, decorators, semantic) {
return true;
if !decorators.is_empty() {
if runtime_evaluated_decorators(decorators, semantic) {
return true;
}
}
false
}
fn runtime_evaluated_base_class(
class_def: &ast::StmtClassDef,
base_classes: &[String],
semantic: &SemanticModel,
) -> bool {
fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticModel) -> bool {
fn inner(
class_def: &ast::StmtClassDef,
base_classes: &[String],
@@ -79,21 +78,19 @@ fn runtime_evaluated_base_class(
})
}
if base_classes.is_empty() {
return false;
}
inner(class_def, base_classes, semantic, &mut FxHashSet::default())
semantic
.current_scope()
.kind
.as_class()
.is_some_and(|class_def| {
inner(class_def, base_classes, semantic, &mut FxHashSet::default())
})
}
fn runtime_evaluated_decorators(
class_def: &ast::StmtClassDef,
decorators: &[String],
semantic: &SemanticModel,
) -> bool {
if decorators.is_empty() {
fn runtime_evaluated_decorators(decorators: &[String], semantic: &SemanticModel) -> bool {
let ScopeKind::Class(class_def) = &semantic.current_scope().kind else {
return false;
}
};
class_def.decorator_list.iter().any(|decorator| {
semantic
@@ -105,72 +102,3 @@ fn runtime_evaluated_decorators(
})
})
}
/// Returns `true` if a function is registered as a `singledispatch` interface.
///
/// For example, `fun` below is a `singledispatch` interface:
/// ```python
/// from functools import singledispatch
///
/// @singledispatch
/// def fun(arg, verbose=False):
/// ...
/// ```
pub(crate) fn is_singledispatch_interface(
function_def: &ast::StmtFunctionDef,
semantic: &SemanticModel,
) -> bool {
function_def.decorator_list.iter().any(|decorator| {
semantic
.resolve_call_path(&decorator.expression)
.is_some_and(|call_path| {
matches!(call_path.as_slice(), ["functools", "singledispatch"])
})
})
}
/// Returns `true` if a function is registered as a `singledispatch` implementation.
///
/// For example, `_` below is a `singledispatch` implementation:
/// For example:
/// ```python
/// from functools import singledispatch
///
/// @singledispatch
/// def fun(arg, verbose=False):
/// ...
///
/// @fun.register
/// def _(arg: int, verbose=False):
/// ...
/// ```
pub(crate) fn is_singledispatch_implementation(
function_def: &ast::StmtFunctionDef,
semantic: &SemanticModel,
) -> bool {
function_def.decorator_list.iter().any(|decorator| {
let Expr::Attribute(attribute) = &decorator.expression else {
return false;
};
if attribute.attr.as_str() != "register" {
return false;
};
let Some(id) = semantic.lookup_attribute(attribute.value.as_ref()) else {
return false;
};
let binding = semantic.binding(id);
let Some(function_def) = binding
.kind
.as_function_definition()
.map(|id| &semantic.scopes[*id])
.and_then(|scope| scope.kind.as_function())
else {
return false;
};
is_singledispatch_interface(function_def, semantic)
})
}

View File

@@ -37,7 +37,6 @@ mod tests {
#[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("TCH003.py"))]
#[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("snapshot.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("TCH002.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("singledispatch.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("strict.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("typing_modules_1.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("typing_modules_2.py"))]

View File

@@ -1,27 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
---
singledispatch.py:10:20: TCH002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
|
8 | from numpy.typing import ArrayLike
9 | from scipy.sparse import spmatrix
10 | from pandas import DataFrame
| ^^^^^^^^^ TCH002
11 |
12 | if TYPE_CHECKING:
|
= help: Move into type-checking block
Unsafe fix
7 7 | from numpy import asarray
8 8 | from numpy.typing import ArrayLike
9 9 | from scipy.sparse import spmatrix
10 |-from pandas import DataFrame
11 10 |
12 11 | if TYPE_CHECKING:
12 |+ from pandas import DataFrame
13 13 | from numpy import ndarray
14 14 |
15 15 |

View File

@@ -69,7 +69,7 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
return;
};
if matches!(value.to_str(), "" | ".") {
if matches!(value.as_str(), "" | ".") {
let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, *range);
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(*range)));
checker.diagnostics.push(diagnostic);

View File

@@ -705,6 +705,8 @@ impl Violation for OsReadlink {
/// ## Examples
/// ```python
/// import os
///
/// import os
/// from pwd import getpwuid
/// from grp import getgrgid
///
@@ -717,6 +719,8 @@ impl Violation for OsReadlink {
/// ```python
/// from pathlib import Path
///
/// from pathlib import Path
///
/// file_path = Path(file_name)
/// stat = file_path.stat()
/// owner_name = file_path.owner()

View File

@@ -67,7 +67,7 @@ fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> {
.iter()
.filter_map(|expr| {
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = expr {
Some(value.to_str())
Some(value.as_str())
} else {
None
}

View File

@@ -1138,47 +1138,4 @@ mod tests {
assert_messages!(diagnostics);
Ok(())
}
#[test_case(Path::new("length_sort_straight_imports.py"))]
#[test_case(Path::new("length_sort_from_imports.py"))]
#[test_case(Path::new("length_sort_straight_and_from_imports.py"))]
#[test_case(Path::new("length_sort_non_ascii_members.py"))]
#[test_case(Path::new("length_sort_non_ascii_modules.py"))]
#[test_case(Path::new("length_sort_with_relative_imports.py"))]
fn length_sort(path: &Path) -> Result<()> {
let snapshot = format!("length_sort__{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("isort").join(path).as_path(),
&LinterSettings {
isort: super::settings::Settings {
length_sort: true,
..super::settings::Settings::default()
},
src: vec![test_resource_path("fixtures/isort")],
..LinterSettings::for_rule(Rule::UnsortedImports)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test_case(Path::new("length_sort_straight_imports.py"))]
#[test_case(Path::new("length_sort_from_imports.py"))]
#[test_case(Path::new("length_sort_straight_and_from_imports.py"))]
fn length_sort_straight(path: &Path) -> Result<()> {
let snapshot = format!("length_sort_straight__{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("isort").join(path).as_path(),
&LinterSettings {
isort: super::settings::Settings {
length_sort_straight: true,
..super::settings::Settings::default()
},
src: vec![test_resource_path("fixtures/isort")],
..LinterSettings::for_rule(Rule::UnsortedImports)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,4 +1,3 @@
use crate::rules::isort::sorting::ImportStyle;
use itertools::Itertools;
use super::settings::Settings;
@@ -57,34 +56,21 @@ pub(crate) fn order_imports<'a>(
.map(Import)
.chain(from_imports.map(ImportFrom))
.sorted_by_cached_key(|import| match import {
Import((alias, _)) => ModuleKey::from_module(
Some(alias.name),
alias.asname,
None,
None,
ImportStyle::Straight,
settings,
),
Import((alias, _)) => {
ModuleKey::from_module(Some(alias.name), alias.asname, None, None, settings)
}
ImportFrom((import_from, _, _, aliases)) => ModuleKey::from_module(
import_from.module,
None,
import_from.level,
aliases.first().map(|(alias, _)| (alias.name, alias.asname)),
ImportStyle::From,
settings,
),
})
.collect()
} else {
let ordered_straight_imports = straight_imports.sorted_by_cached_key(|(alias, _)| {
ModuleKey::from_module(
Some(alias.name),
alias.asname,
None,
None,
ImportStyle::Straight,
settings,
)
ModuleKey::from_module(Some(alias.name), alias.asname, None, None, settings)
});
let ordered_from_imports =
from_imports.sorted_by_cached_key(|(import_from, _, _, aliases)| {
@@ -93,7 +79,6 @@ pub(crate) fn order_imports<'a>(
None,
import_from.level,
aliases.first().map(|(alias, _)| (alias.name, alias.asname)),
ImportStyle::From,
settings,
)
});

View File

@@ -58,8 +58,6 @@ pub struct Settings {
pub section_order: Vec<ImportSection>,
pub no_sections: bool,
pub from_first: bool,
pub length_sort: bool,
pub length_sort_straight: bool,
}
impl Default for Settings {
@@ -88,8 +86,6 @@ impl Default for Settings {
section_order: ImportType::iter().map(ImportSection::Known).collect(),
no_sections: false,
from_first: false,
length_sort: false,
length_sort_straight: false,
}
}
}

View File

@@ -1,18 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / from mediuuuuuuuuuuum import a
2 | | from short import b
3 | | from loooooooooooooooooooooog import c
|
= help: Organize imports
Safe fix
1 |+from short import b
1 2 | from mediuuuuuuuuuuum import a
2 |-from short import b
3 3 | from loooooooooooooooooooooog import c

View File

@@ -1,37 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_non_ascii_members.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / from module1 import (
2 | | loooooooooooooong,
3 | | σηορτ,
4 | | mediuuuuum,
5 | | shoort,
6 | | looooooooooooooong,
7 | | μεδιυυυυυμ,
8 | | short,
9 | | mediuuuuuum,
10 | | λοοοοοοοοοοοοοονγ,
11 | | )
|
= help: Organize imports
Safe fix
1 1 | from module1 import (
2 |- loooooooooooooong,
2 |+ short,
3 3 | σηορτ,
4 |+ shoort,
4 5 | mediuuuuum,
5 |- shoort,
6 |- looooooooooooooong,
7 6 | μεδιυυυυυμ,
8 |- short,
9 7 | mediuuuuuum,
8 |+ loooooooooooooong,
10 9 | λοοοοοοοοοοοοοονγ,
10 |+ looooooooooooooong,
11 11 | )

View File

@@ -1,32 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_non_ascii_modules.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import loooooooooooooong
2 | | import mediuuuuuum
3 | | import short
4 | | import σηορτ
5 | | import shoort
6 | | import mediuuuuum
7 | | import λοοοοοοοοοοοοοονγ
8 | | import μεδιυυυυυμ
9 | | import looooooooooooooong
|
= help: Organize imports
Safe fix
1 |-import loooooooooooooong
2 |-import mediuuuuuum
3 1 | import short
4 2 | import σηορτ
5 3 | import shoort
6 4 | import mediuuuuum
5 |+import μεδιυυυυυμ
6 |+import mediuuuuuum
7 |+import loooooooooooooong
7 8 | import λοοοοοοοοοοοοοονγ
8 |-import μεδιυυυυυμ
9 9 | import looooooooooooooong

View File

@@ -1,26 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_straight_and_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import mediuuuuuum
2 | | import short
3 | | import looooooooooooooooong
4 | | from looooooooooooooong import a
5 | | from mediuuuum import c
6 | | from short import b
|
= help: Organize imports
Safe fix
1 |+import short
1 2 | import mediuuuuuum
2 |-import short
3 3 | import looooooooooooooooong
4 |-from looooooooooooooong import a
4 |+from short import b
5 5 | from mediuuuum import c
6 |-from short import b
6 |+from looooooooooooooong import a

View File

@@ -1,21 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_straight_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import mediuuuuuumb
2 | | import short
3 | | import looooooooooooooooong
4 | | import mediuuuuuuma
|
= help: Organize imports
Safe fix
1 |+import short
2 |+import mediuuuuuuma
1 3 | import mediuuuuuumb
2 |-import short
3 4 | import looooooooooooooooong
4 |-import mediuuuuuuma

View File

@@ -1,28 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_with_relative_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / from ..looooooooooooooong import a
2 | | from ...mediuuuum import b
3 | | from .short import c
4 | | from ....short import c
5 | | from . import d
6 | | from .mediuuuum import a
7 | | from ......short import b
|
= help: Organize imports
Safe fix
1 |-from ..looooooooooooooong import a
2 |-from ...mediuuuum import b
1 |+from . import d
3 2 | from .short import c
4 3 | from ....short import c
5 |-from . import d
6 4 | from .mediuuuum import a
7 5 | from ......short import b
6 |+from ...mediuuuum import b
7 |+from ..looooooooooooooong import a

View File

@@ -1,18 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / from mediuuuuuuuuuuum import a
2 | | from short import b
3 | | from loooooooooooooooooooooog import c
|
= help: Organize imports
Safe fix
1 |+from loooooooooooooooooooooog import c
1 2 | from mediuuuuuuuuuuum import a
2 3 | from short import b
3 |-from loooooooooooooooooooooog import c

View File

@@ -1,23 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_straight_and_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import mediuuuuuum
2 | | import short
3 | | import looooooooooooooooong
4 | | from looooooooooooooong import a
5 | | from mediuuuum import c
6 | | from short import b
|
= help: Organize imports
Safe fix
1 |+import short
1 2 | import mediuuuuuum
2 |-import short
3 3 | import looooooooooooooooong
4 4 | from looooooooooooooong import a
5 5 | from mediuuuum import c

View File

@@ -1,21 +0,0 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
length_sort_straight_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
1 | / import mediuuuuuumb
2 | | import short
3 | | import looooooooooooooooong
4 | | import mediuuuuuuma
|
= help: Organize imports
Safe fix
1 |+import short
2 |+import mediuuuuuuma
1 3 | import mediuuuuuumb
2 |-import short
3 4 | import looooooooooooooooong
4 |-import mediuuuuuuma

View File

@@ -3,7 +3,6 @@
use std::{borrow::Cow, cmp::Ordering, cmp::Reverse};
use natord;
use unicode_width::UnicodeWidthStr;
use ruff_python_stdlib::str;
@@ -65,27 +64,18 @@ impl<'a> From<String> for NatOrdStr<'a> {
}
}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
pub(crate) enum Distance {
Nearest(u32),
Furthest(Reverse<u32>),
}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub(crate) enum ImportStyle {
// Ex) `import foo`
Straight,
// Ex) `from foo import bar`
From,
}
/// A comparable key to capture the desired sorting order for an imported module (e.g.,
/// `foo` in `from foo import bar`).
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
pub(crate) struct ModuleKey<'a> {
force_to_top: bool,
maybe_length: Option<usize>,
distance: Distance,
force_to_top: Option<bool>,
maybe_lowercase_name: Option<NatOrdStr<'a>>,
module_name: Option<NatOrdStr<'a>>,
first_alias: Option<MemberKey<'a>>,
@@ -98,39 +88,26 @@ impl<'a> ModuleKey<'a> {
asname: Option<&'a str>,
level: Option<u32>,
first_alias: Option<(&'a str, Option<&'a str>)>,
style: ImportStyle,
settings: &Settings,
) -> Self {
let level = level.unwrap_or_default();
let force_to_top = !name
.map(|name| settings.force_to_top.contains(name))
.unwrap_or_default(); // `false` < `true` so we get forced to top first
let maybe_length = (settings.length_sort
|| (settings.length_sort_straight && style == ImportStyle::Straight))
.then_some(name.map(str::width).unwrap_or_default() + level as usize);
let distance = match settings.relative_imports_order {
RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level),
RelativeImportsOrder::FurthestToClosest => Distance::Furthest(Reverse(level)),
RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level.unwrap_or_default()),
RelativeImportsOrder::FurthestToClosest => {
Distance::Furthest(Reverse(level.unwrap_or_default()))
}
};
let force_to_top = name.map(|name| !settings.force_to_top.contains(name)); // `false` < `true` so we get forced to top first
let maybe_lowercase_name = name.and_then(|name| {
(!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name)))
});
let module_name = name.map(NatOrdStr::from);
let asname = asname.map(NatOrdStr::from);
let first_alias =
first_alias.map(|(name, asname)| MemberKey::from_member(name, asname, settings));
Self {
force_to_top,
maybe_length,
distance,
force_to_top,
maybe_lowercase_name,
module_name,
first_alias,
@@ -145,7 +122,6 @@ impl<'a> ModuleKey<'a> {
pub(crate) struct MemberKey<'a> {
not_star_import: bool,
member_type: Option<MemberType>,
maybe_length: Option<usize>,
maybe_lowercase_name: Option<NatOrdStr<'a>>,
module_name: NatOrdStr<'a>,
asname: Option<NatOrdStr<'a>>,
@@ -157,7 +133,6 @@ impl<'a> MemberKey<'a> {
let member_type = settings
.order_by_type
.then_some(member_type(name, settings));
let maybe_length = settings.length_sort.then_some(name.width());
let maybe_lowercase_name =
(!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name)));
let module_name = NatOrdStr::from(name);
@@ -166,7 +141,6 @@ impl<'a> MemberKey<'a> {
Self {
not_star_import,
member_type,
maybe_length,
maybe_lowercase_name,
module_name,
asname,

View File

@@ -1,5 +1,4 @@
use itertools::Itertools;
use ruff_python_ast::call_path::collect_call_path;
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use ruff_python_semantic::SemanticModel;
@@ -73,7 +72,6 @@ pub(super) fn is_type_alias_assignment(stmt: &Stmt, semantic: &SemanticModel) ->
}
}
/// Returns `true` if the statement is an assignment to a `TypedDict`.
pub(super) fn is_typed_dict_class(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool {
arguments.is_some_and(|arguments| {
arguments
@@ -83,67 +81,6 @@ pub(super) fn is_typed_dict_class(arguments: Option<&Arguments>, semantic: &Sema
})
}
/// Returns `true` if a statement appears to be a dynamic import of a Django model.
///
/// For example, in Django, it's common to use `get_model` to access a model dynamically, as in:
/// ```python
/// def migrate_existing_attachment_data(
/// apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
/// ) -> None:
/// Attachment = apps.get_model("zerver", "Attachment")
/// ```
pub(super) fn is_django_model_import(name: &str, stmt: &Stmt, semantic: &SemanticModel) -> bool {
fn match_model_import(name: &str, expr: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Call(ast::ExprCall {
func, arguments, ..
}) = expr
else {
return false;
};
// Match against, e.g., `apps.get_model("zerver", "Attachment")`.
if let Some(call_path) = collect_call_path(func.as_ref()) {
if matches!(call_path.as_slice(), [.., "get_model"]) {
if let Some(argument) =
arguments.find_argument("model_name", arguments.args.len() - 1)
{
if let Some(string_literal) = argument.as_string_literal_expr() {
return string_literal.value.to_str() == name;
}
}
}
}
// Match against, e.g., `import_string("zerver.models.Attachment")`.
if let Some(call_path) = semantic.resolve_call_path(func.as_ref()) {
if matches!(
call_path.as_slice(),
["django", "utils", "module_loading", "import_string"]
) {
if let Some(argument) = arguments.find_argument("dotted_path", 0) {
if let Some(string_literal) = argument.as_string_literal_expr() {
if let Some((.., model)) = string_literal.value.to_str().rsplit_once('.') {
return model == name;
}
}
}
}
}
false
}
match stmt {
Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value), ..
}) => match_model_import(name, value.as_ref(), semantic),
Stmt::Assign(ast::StmtAssign { value, .. }) => {
match_model_import(name, value.as_ref(), semantic)
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::{is_acronym, is_camelcase, is_mixed_case};

View File

@@ -53,15 +53,6 @@ impl Violation for NonLowercaseVariableInFunction {
/// N806
pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &Expr, name: &str) {
// Ignore globals.
if checker
.semantic()
.lookup_symbol(name)
.is_some_and(|id| checker.semantic().binding(id).is_global())
{
return;
}
if checker
.settings
.pep8_naming
@@ -81,7 +72,6 @@ pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &E
|| helpers::is_typed_dict_assignment(parent, checker.semantic())
|| helpers::is_type_var_assignment(parent, checker.semantic())
|| helpers::is_type_alias_assignment(parent, checker.semantic())
|| helpers::is_django_model_import(name, parent, checker.semantic())
{
return;
}

View File

@@ -20,22 +20,4 @@ N806.py:13:5: N806 Variable `CONSTANT` in function should be lowercase
14 | _ = 0
|
N806.py:46:5: N806 Variable `Bad` in function should be lowercase
|
45 | def model_assign() -> None:
46 | Bad = apps.get_model("zerver", "Stream") # N806
| ^^^ N806
47 | Attachment = apps.get_model("zerver", "Attachment") # OK
48 | Recipient = apps.get_model("zerver", model_name="Recipient") # OK
|
N806.py:53:5: N806 Variable `Bad` in function should be lowercase
|
51 | from django.utils.module_loading import import_string
52 |
53 | Bad = import_string("django.core.exceptions.ValidationError") # N806
| ^^^ N806
54 | ValidationError = import_string("django.core.exceptions.ValidationError") # OK
|

View File

@@ -37,7 +37,6 @@ mod tests {
#[test_case(Rule::MixedSpacesAndTabs, Path::new("E101.py"))]
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E40.py"))]
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.py"))]
#[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.ipynb"))]
#[test_case(Rule::MultipleImportsOnOneLine, Path::new("E40.py"))]
#[test_case(Rule::MultipleStatementsOnOneLineColon, Path::new("E70.py"))]
#[test_case(Rule::MultipleStatementsOnOneLineSemicolon, Path::new("E70.py"))]

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