Compare commits
1 Commits
charlie/sp
...
zb/fmt-ski
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c8017ef13 |
35
.github/workflows/ci.yaml
vendored
35
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),+))
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,10 +26,5 @@ def func():
|
||||
|
||||
from trio import Event, sleep
|
||||
|
||||
|
||||
def func():
|
||||
sleep(0) # TRIO115
|
||||
|
||||
|
||||
async def func():
|
||||
await sleep(seconds=0) # TRIO115
|
||||
|
||||
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
from mediuuuuuuuuuuum import a
|
||||
from short import b
|
||||
from loooooooooooooooooooooog import c
|
||||
@@ -1,11 +0,0 @@
|
||||
from module1 import (
|
||||
loooooooooooooong,
|
||||
σηορτ,
|
||||
mediuuuuum,
|
||||
shoort,
|
||||
looooooooooooooong,
|
||||
μεδιυυυυυμ,
|
||||
short,
|
||||
mediuuuuuum,
|
||||
λοοοοοοοοοοοοοονγ,
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
import loooooooooooooong
|
||||
import mediuuuuuum
|
||||
import short
|
||||
import σηορτ
|
||||
import shoort
|
||||
import mediuuuuum
|
||||
import λοοοοοοοοοοοοοονγ
|
||||
import μεδιυυυυυμ
|
||||
import looooooooooooooong
|
||||
@@ -1,6 +0,0 @@
|
||||
import mediuuuuuum
|
||||
import short
|
||||
import looooooooooooooooong
|
||||
from looooooooooooooong import a
|
||||
from mediuuuum import c
|
||||
from short import b
|
||||
@@ -1,4 +0,0 @@
|
||||
import mediuuuuuumb
|
||||
import short
|
||||
import looooooooooooooooong
|
||||
import mediuuuuuuma
|
||||
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
from looooooooooooooong import a
|
||||
from mediuuuum import *
|
||||
from short import *
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
class Platform:
|
||||
""" Remove sampler
|
||||
Args:
|
||||
Returns:
|
||||
"""
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(¶meters.kwonlyargs)
|
||||
{
|
||||
if let Some(expr) = ¶meter_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) = ¶meter_with_default.default {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
singledispatch = false;
|
||||
}
|
||||
if let Some(arg) = ¶meters.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(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 | )
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user