Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Gallant
6d8acee9db [ty] Demonstrate SIGSEGV via salsa
This is a demonstration of what I believe must imply unsoundness
somewhere inside of Salsa. That is, I don't use any `unsafe`, but I get
an "invalid memory reference" error.

Here is the backtrace I get from `gdb`:

```
(gdb) bt
 #0  ty_python_semantic::module_resolver::path::{impl#33}::eq (self=<error reading variable: Cannot access memory at address 0x7ff80efb0bd0>, other=0x7ffff106fcb0) at crates/ty_python_semantic/src/module_resolver/path.rs:452
 #1  0x0000555555ccc9fb in alloc::sync::{impl#50}::eq<ty_python_semantic::module_resolver::path::SearchPathInner, alloc::alloc::Global> (self=0x7ffff0110470, other=0x7ffff1033660)
     at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/sync.rs:3358
 #2  0x0000555555ccc6a4 in alloc::sync::{impl#51}::eq<ty_python_semantic::module_resolver::path::SearchPathInner, alloc::alloc::Global> (self=0x7ffff0110470, other=0x7ffff1033660)
     at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/sync.rs:3388
 #3  0x0000555555b92744 in ty_python_semantic::module_resolver::path::{impl#40}::eq (self=0x7ffff0110470, other=0x7ffff1033660) at crates/ty_python_semantic/src/module_resolver/path.rs:491
 #4  0x00005555562a4239 in core::cmp::impls::{impl#9}::eq<ty_python_semantic::module_resolver::path::SearchPath, ty_python_semantic::module_resolver::path::SearchPath> (self=0x7ffff0e88f50, other=0x7ffff7c755c8)
     at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cmp.rs:2027
 #5  0x00005555562a4436 in core::tuple::{impl#10}::eq<(), &ty_python_semantic::module_resolver::path::SearchPath> (self=0x7ffff0e88f50, other=0x7ffff7c755c8)
     at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/tuple.rs:32
 #6  0x00005555562a44f2 in core::cmp::impls::{impl#9}::eq<((), &ty_python_semantic::module_resolver::path::SearchPath), ((), &ty_python_semantic::module_resolver::path::SearchPath)> (self=0x7ffff7c75178, other=0x7ffff7c75180)
     at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cmp.rs:2027
 #7  salsa::interned::{impl#14}::eq<((), &ty_python_semantic::module_resolver::path::SearchPath)> (self=0x7ffff0e88f50, data=0x7ffff7c755c8) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/interned.rs:1161
 #8  0x0000555555c172e7 in salsa::interned::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_>::value_eq<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_, ((), &ty_python_semantic::module_resolver::path::SearchPath)> (id=..., key=0x7ffff7c755c8, zalsa=0x7ffff000daa0, found_value=0x7ffff7c755e8)
     at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/interned.rs:763
 #9  0x0000555555ca12b7 in salsa::interned::{impl#7}::intern_id::{closure#0}<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_, ((), &ty_python_semantic::module_resolver::path::SearchPath), ty_python_semantic::module_resolver::list::list_modules_in::{closure#0}::{closure_env#0}> (id=0x7ffff0e3a588) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/interned.rs:374
 #10 0x00005555565a907e in hashbrown::raw::{impl#8}::find::{closure#0}<salsa::id::Id, allocator_api2::stable::alloc::global::Global, salsa::interned::{impl#7}::intern_id::{closure_env#0}<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_, ((), &ty_python_semantic::module_resolver::path::SearchPath), ty_python_semantic::module_resolver::list::list_modules_in::{closure#0}::{closure_env#0}>> (index=0)
     at /home/andrew/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hashbrown-0.15.5/src/raw/mod.rs:1197
 #11 0x00005555565821ec in hashbrown::raw::RawTableInner::find_inner (self=0x7ffff0098e08, hash=16887370829962692076, eq=...) at /home/andrew/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hashbrown-0.15.5/src/raw/mod.rs:1921
 #12 hashbrown::raw::RawTable<salsa::id::Id, allocator_api2::stable::alloc::global::Global>::find<salsa::id::Id, allocator_api2::stable::alloc::global::Global, salsa::interned::{impl#7}::intern_id::{closure_env#0}<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_, ((), &ty_python_semantic::module_resolver::path::SearchPath), ty_python_semantic::module_resolver::list::list_modules_in::{closure#0}::{closure_env#0}>> (
     self=0x7ffff0098e08, hash=16887370829962692076, eq=...) at /home/andrew/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hashbrown-0.15.5/src/raw/mod.rs:1197
 #13 0x0000555555c4f7f7 in hashbrown::raw::RawTable<salsa::id::Id, allocator_api2::stable::alloc::global::Global>::get<salsa::id::Id, allocator_api2::stable::alloc::global::Global, salsa::interned::{impl#7}::intern_id::{closure_env#0}<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_, ((), &ty_python_semantic::module_resolver::path::SearchPath), ty_python_semantic::module_resolver::list::list_modules_in::{closure#0}::{closure_env#0}>> (self=0x7ffff0098e08, hash=16887370829962692076, eq=...) at /home/andrew/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hashbrown-0.15.5/src/raw/mod.rs:1212
 #14 hashbrown::table::HashTable<salsa::id::Id, allocator_api2::stable::alloc::global::Global>::find<salsa::id::Id, allocator_api2::stable::alloc::global::Global, salsa::interned::{impl#7}::intern_id::{closure_env#0}<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_, ((), &ty_python_semantic::module_resolver::path::SearchPath), ty_python_semantic::module_resolver::list::list_modules_in::{closure#0}::{closure_env#0}>> (
     self=0x7ffff0098e08, hash=16887370829962692076, eq=...) at /home/andrew/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hashbrown-0.15.5/src/table.rs:224
 #15 salsa::interned::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_>::intern_id<ty_python_semantic::module_resolver::list::list_modules_in::list_modules_in_Configuration_, ((), &ty_python_semantic::module_resolver::path::SearchPath), ty_python_semantic::module_resolver::list::list_modules_in::{closure#0}::{closure_env#0}> (self=0x7ffff00982c0, zalsa=0x7ffff000daa0, zalsa_local=0x7ffff7c785a8,
     key=..., assemble=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/interned.rs:377
 #16 0x0000555555a24e68 in ty_python_semantic::module_resolver::list::list_modules_in::{closure#0} () at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/components/salsa-macro-rules/src/setup_tracked_fn.rs:473
 #17 0x000055555647410c in salsa::attach::Attached::attach<dyn ty_python_semantic::db::Db, alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, ty_python_semantic::module_resolver::list::list_modules_in::{closure_env#0}> (self=0x7ffff7c7a618, db=..., op=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/attach.rs:79
 #18 0x0000555556470f92 in salsa::attach::attach::{closure#0}<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules_in::{closure_env#0}> (a=0x7ffff7c7a618) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/attach.rs:103
 #19 0x0000555556304feb in std::thread::local::LocalKey<salsa::attach::Attached>::try_with<salsa::attach::Attached, salsa::attach::attach::{closure_env#0}<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules_in::{closure_env#0}>, alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>> (
     self=0x555557db8810, f=...) at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:315
 #20 0x00005555562e7c8d in std::thread::local::LocalKey<salsa::attach::Attached>::with<salsa::attach::Attached, salsa::attach::attach::{closure_env#0}<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules_in::{closure_env#0}>, alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>> (
     self=0x555557db8810, f=<error reading variable: Cannot access memory at address 0x0>) at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:279
 #21 0x000055555646f5e4 in salsa::attach::attach<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules_in::{closure_env#0}> (db=..., op=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/attach.rs:101
 #22 0x0000555555cc9f94 in ty_python_semantic::module_resolver::list::list_modules_in (db=..., search_path=0x7ffff1033660)
     at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/components/salsa-macro-rules/src/setup_tracked_fn.rs:468
 #23 0x0000555555cc95b5 in ty_python_semantic::module_resolver::list::list_modules::{impl#2}::execute::inner_ (db=...) at crates/ty_python_semantic/src/module_resolver/list.rs:20
 #24 0x0000555555cc946b in ty_python_semantic::module_resolver::list::list_modules::{impl#2}::execute (db=...)
     at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/components/salsa-macro-rules/src/setup_tracked_fn.rs:302
 #25 0x00005555561c575a in salsa::function::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>::execute_query<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_> (db=..., zalsa=0x7ffff000daa0, active_query=..., opt_old_memo=..., id=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/execute.rs:311
 #26 0x0000555556211b9d in salsa::function::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>::execute<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_> (self=0x7ffff0098010, db=..., active_query=..., opt_old_memo=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/execute.rs:46
 #27 0x0000555556143974 in salsa::function::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>::fetch_cold<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_> (self=0x7ffff0098010, zalsa=0x7ffff000daa0, zalsa_local=0x7ffff7c785a8, db=..., id=..., memo_ingredient_index=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/fetch.rs:235
 #28 0x00005555561775d0 in salsa::function::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>::fetch_cold_with_retry<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_> (self=0x7ffff0098010, zalsa=0x7ffff000daa0, zalsa_local=0x7ffff7c785a8, db=..., id=..., memo_ingredient_index=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/fetch.rs:107
 #29 0x0000555556754069 in salsa::function::fetch::{impl#0}::refresh_memo::{closure#0}<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_> ()
     at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/fetch.rs:64
 #30 0x00005555562d0891 in core::option::Option<&salsa::function::memo::Memo<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>>::or_else<&salsa::function::memo::Memo<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>, salsa::function::fetch::{impl#0}::refresh_memo::{closure_env#0}<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>> (self=..., f=...)
 --Type <RET> for more, q to quit, c to continue without paging--
     at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:1609
 #31 0x0000555556183e04 in salsa::function::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>::refresh_memo<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_> (self=0x7ffff0098010, db=..., zalsa=0x7ffff000daa0, zalsa_local=0x7ffff7c785a8, id=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/fetch.rs:63
 #32 salsa::function::IngredientImpl<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_>::fetch<ty_python_semantic::module_resolver::list::list_modules::list_modules_Configuration_> (
     self=0x7ffff0098010, db=..., zalsa=0x7ffff000daa0, zalsa_local=0x7ffff7c785a8, id=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/fetch.rs:30
 #33 0x0000555555a24d85 in ty_python_semantic::module_resolver::list::list_modules::{closure#0} () at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/components/salsa-macro-rules/src/setup_tracked_fn.rs:474
 #34 0x0000555556474dd4 in salsa::attach::Attached::attach<dyn ty_python_semantic::db::Db, alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, ty_python_semantic::module_resolver::list::list_modules::{closure_env#0}> (self=0x7ffff7c7a618, db=..., op=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/attach.rs:79
 #35 0x0000555556470491 in salsa::attach::attach::{closure#0}<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules::{closure_env#0}> (a=0x7ffff7c7a618) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/attach.rs:103
 #36 0x00005555563064e6 in std::thread::local::LocalKey<salsa::attach::Attached>::try_with<salsa::attach::Attached, salsa::attach::attach::{closure_env#0}<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules::{closure_env#0}>, alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>> (
     self=0x555557db8810, f=...) at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:315
 #37 0x00005555562e897d in std::thread::local::LocalKey<salsa::attach::Attached>::with<salsa::attach::Attached, salsa::attach::attach::{closure_env#0}<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules::{closure_env#0}>, alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>> (self=0x555557db8810,
     f=<error reading variable: Cannot access memory at address 0x0>) at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:279
 #38 0x000055555646f265 in salsa::attach::attach<alloc::vec::Vec<ty_python_semantic::module_resolver::module::Module, alloc::alloc::Global>, dyn ty_python_semantic::db::Db, ty_python_semantic::module_resolver::list::list_modules::{closure_env#0}> (db=..., op=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/attach.rs:101
 #39 0x0000555555cc93c7 in ty_python_semantic::module_resolver::list::list_modules (db=...) at /home/andrew/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/components/salsa-macro-rules/src/setup_tracked_fn.rs:468
 #40 0x00005555557e2186 in ty_test::run_module_resolution_consistency_test (db=0x7ffff7c78598) at crates/ty_test/src/lib.rs:450
 #41 0x00005555557defec in ty_test::run (absolute_fixture_path=..., relative_fixture_path=..., snapshot_path=..., short_title=..., test_name=..., output_format=ty_test::OutputFormat::Cli) at crates/ty_test/src/lib.rs:73
 #42 0x0000555555793c06 in mdtest::mdtest (fixture=...) at crates/ty_python_semantic/tests/mdtest.rs:29
 #43 0x00005555557959c1 in mdtest::mdtest__generics_pep695_classes () at crates/ty_python_semantic/tests/mdtest.rs:7
 #44 0x00005555557903a7 in mdtest::mdtest__generics_pep695_classes::{closure#0} () at crates/ty_python_semantic/tests/mdtest.rs:10
 #45 0x000055555579a1b6 in core::ops::function::FnOnce::call_once<mdtest::mdtest__generics_pep695_classes::{closure_env#0}, ()> ()
     at /home/andrew/.rustup/toolchains/1.89-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250
 #46 0x00005555557d98eb in core::ops::function::FnOnce::call_once<fn() -> core::result::Result<(), alloc::string::String>, ()> () at library/core/src/ops/function.rs:250
 #47 test::__rust_begin_short_backtrace<core::result::Result<(), alloc::string::String>, fn() -> core::result::Result<(), alloc::string::String>> () at library/test/src/lib.rs:648
 #48 0x00005555557d8b6e in test::run_test_in_process::{closure#0} () at library/test/src/lib.rs:671
 #49 core::panic::unwind_safe::{impl#23}::call_once<core::result::Result<(), alloc::string::String>, test::run_test_in_process::{closure_env#0}> () at library/core/src/panic/unwind_safe.rs:272
 #50 std::panicking::catch_unwind::do_call<core::panic::unwind_safe::AssertUnwindSafe<test::run_test_in_process::{closure_env#0}>, core::result::Result<(), alloc::string::String>> () at library/std/src/panicking.rs:589
 #51 std::panicking::catch_unwind<core::result::Result<(), alloc::string::String>, core::panic::unwind_safe::AssertUnwindSafe<test::run_test_in_process::{closure_env#0}>> () at library/std/src/panicking.rs:552
 #52 std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<test::run_test_in_process::{closure_env#0}>, core::result::Result<(), alloc::string::String>> () at library/std/src/panic.rs:359
 #53 test::run_test_in_process () at library/test/src/lib.rs:671
 #54 test::run_test::{closure#0} () at library/test/src/lib.rs:592
 #55 0x000055555579d944 in test::run_test::{closure#1} () at library/test/src/lib.rs:622
 #56 std::sys::backtrace::__rust_begin_short_backtrace<test::run_test::{closure_env#1}, ()> () at library/std/src/sys/backtrace.rs:152
 #57 0x00005555557a10ca in std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure#0}<test::run_test::{closure_env#1}, ()> () at library/std/src/thread/mod.rs:559
 #58 core::panic::unwind_safe::{impl#23}::call_once<(), std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<test::run_test::{closure_env#1}, ()>> () at library/core/src/panic/unwind_safe.rs:272
 #59 std::panicking::catch_unwind::do_call<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<test::run_test::{closure_env#1}, ()>>, ()> ()
     at library/std/src/panicking.rs:589
 #60 std::panicking::catch_unwind<(), core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<test::run_test::{closure_env#1}, ()>>> () at library/std/src/panicking.rs:552
 #61 std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<test::run_test::{closure_env#1}, ()>>, ()> () at library/std/src/panic.rs:359
 #62 std::thread::{impl#0}::spawn_unchecked_::{closure#1}<test::run_test::{closure_env#1}, ()> () at library/std/src/thread/mod.rs:557
 #63 core::ops::function::FnOnce::call_once<std::thread::{impl#0}::spawn_unchecked_::{closure_env#1}<test::run_test::{closure_env#1}, ()>, ()> () at library/core/src/ops/function.rs:250
 #64 0x000055555705088f in alloc::boxed::{impl#28}::call_once<(), dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global> () at library/alloc/src/boxed.rs:1966
 #65 std::sys::pal::unix::thread::{impl#2}::new::thread_start () at library/std/src/sys/pal/unix/thread.rs:107
 #66 0x00007ffff7d127eb in ?? () from /usr/lib/libc.so.6
 #67 0x00007ffff7d9618c in ?? () from /usr/lib/libc.so.6
```
2025-08-20 10:46:45 -04:00
1118 changed files with 28758 additions and 37550 deletions

View File

@@ -441,7 +441,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
with:
tool: cargo-fuzz@0.11.2
- name: "Install cargo-fuzz"
@@ -463,7 +463,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
name: Download Ruff binary to test
id: download-cached-binary
@@ -664,7 +664,7 @@ jobs:
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Fuzz
env:
FORCE_COLOR: 1
@@ -694,7 +694,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
- uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -734,7 +734,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
@@ -777,7 +777,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt --system
@@ -909,7 +909,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Rust toolchain"
run: rustup show
@@ -942,7 +942,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Rust toolchain"
run: rustup show

View File

@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"

View File

@@ -39,7 +39,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
@@ -82,7 +82,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*

View File

@@ -61,7 +61,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive
@@ -124,7 +124,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive
@@ -175,7 +175,7 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive
@@ -251,7 +251,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive

View File

@@ -65,7 +65,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -117,7 +117,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -155,7 +155,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Setup git
run: |
git config --global user.name typeshedbot

View File

@@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -1,29 +1,5 @@
# Changelog
## 0.12.10
### Preview features
- \[`flake8-simplify`\] Implement fix for `maxsplit` without separator (`SIM905`) ([#19851](https://github.com/astral-sh/ruff/pull/19851))
- \[`flake8-use-pathlib`\] Add fixes for `PTH102` and `PTH103` ([#19514](https://github.com/astral-sh/ruff/pull/19514))
### Bug fixes
- \[`isort`\] Handle multiple continuation lines after module docstring (`I002`) ([#19818](https://github.com/astral-sh/ruff/pull/19818))
- \[`pyupgrade`\] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) ([#19769](https://github.com/astral-sh/ruff/pull/19769))
- \[`pyupgrade`\] Handle nested `Optional`s (`UP045`) ([#19770](https://github.com/astral-sh/ruff/pull/19770))
### Rule changes
- \[`pycodestyle`\] Make `E731` fix unsafe instead of display-only for class assignments ([#19700](https://github.com/astral-sh/ruff/pull/19700))
- \[`pyflakes`\] Add secondary annotation showing previous definition (`F811`) ([#19900](https://github.com/astral-sh/ruff/pull/19900))
### Documentation
- Fix description of global config file discovery strategy ([#19188](https://github.com/astral-sh/ruff/pull/19188))
- Update outdated links to <https://typing.python.org/en/latest/source/stubs.html> ([#19992](https://github.com/astral-sh/ruff/pull/19992))
- \[`flake8-annotations`\] Remove unused import in example (`ANN401`) ([#20000](https://github.com/astral-sh/ruff/pull/20000))
## 0.12.9
### Preview features

171
Cargo.lock generated
View File

@@ -257,9 +257,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.3"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
[[package]]
name = "bitvec"
@@ -295,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"regex-automata 0.4.10",
"regex-automata 0.4.9",
"serde",
]
@@ -485,7 +485,7 @@ checksum = "85a8ab73a1c02b0c15597b22e09c7dc36e63b2f601f9d1e83ac0c3decd38b1ae"
dependencies = [
"nix 0.29.0",
"terminfo",
"thiserror 2.0.16",
"thiserror 2.0.12",
"which",
"windows-sys 0.59.0",
]
@@ -955,7 +955,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1035,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1084,14 +1084,14 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.26"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1118,9 +1118,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
@@ -1231,7 +1231,7 @@ dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.10",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
@@ -1241,7 +1241,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"ignore",
"walkdir",
]
@@ -1430,9 +1430,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.1.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
@@ -1459,7 +1459,7 @@ dependencies = [
"globset",
"log",
"memchr",
"regex-automata 0.4.10",
"regex-automata 0.4.9",
"same-file",
"walkdir",
"winapi-util",
@@ -1486,9 +1486,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.11.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
@@ -1521,7 +1521,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"inotify-sys",
"libc",
]
@@ -1780,7 +1780,7 @@ dependencies = [
"paste",
"peg",
"regex",
"thiserror 2.0.16",
"thiserror 2.0.12",
]
[[package]]
@@ -1809,7 +1809,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"libc",
"redox_syscall",
]
@@ -2014,7 +2014,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2026,7 +2026,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2054,7 +2054,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"fsevent-sys",
"inotify",
"kqueue",
@@ -2127,9 +2127,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordermap"
version = "0.5.9"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd6fedcd996c8c97932075cc3811d83f53280f48d5620e4e3cab7f6a12678c4"
checksum = "6d6bff06e4a5dc6416bead102d3e63c480dd852ffbb278bf8cfeb4966b329609"
dependencies = [
"indexmap",
"serde",
@@ -2283,9 +2283,9 @@ dependencies = [
[[package]]
name = "percent-encoding"
version = "2.3.2"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
@@ -2294,7 +2294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
dependencies = [
"memchr",
"thiserror 2.0.16",
"thiserror 2.0.12",
"ucd-trie",
]
@@ -2473,9 +2473,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.101"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0"
dependencies = [
"unicode-ident",
]
@@ -2490,7 +2490,7 @@ dependencies = [
"pep440_rs",
"pep508_rs",
"serde",
"thiserror 2.0.16",
"thiserror 2.0.12",
"toml 0.8.23",
]
@@ -2505,7 +2505,7 @@ dependencies = [
"newtype-uuid",
"quick-xml",
"strip-ansi-escapes",
"thiserror 2.0.16",
"thiserror 2.0.12",
"uuid",
]
@@ -2666,7 +2666,7 @@ version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
]
[[package]]
@@ -2677,18 +2677,18 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.16",
"thiserror 2.0.12",
]
[[package]]
name = "regex"
version = "1.11.2"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.10",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
@@ -2703,9 +2703,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.10"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@@ -2743,13 +2743,13 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.12.10"
version = "0.12.9"
dependencies = [
"anyhow",
"argfile",
"assert_fs",
"bincode 2.0.1",
"bitflags 2.9.3",
"bitflags 2.9.2",
"cachedir",
"clap",
"clap_complete_command",
@@ -2793,7 +2793,7 @@ dependencies = [
"strum",
"tempfile",
"test-case",
"thiserror 2.0.16",
"thiserror 2.0.12",
"tikv-jemallocator",
"toml 0.9.5",
"tracing",
@@ -2886,9 +2886,8 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"similar",
"tempfile",
"thiserror 2.0.16",
"thiserror 2.0.12",
"tracing",
"tracing-subscriber",
"ty_static",
@@ -2935,7 +2934,6 @@ dependencies = [
"tracing-subscriber",
"ty",
"ty_project",
"ty_python_semantic",
"ty_static",
"url",
]
@@ -2998,11 +2996,11 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.12.10"
version = "0.12.9"
dependencies = [
"aho-corasick",
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.2",
"clap",
"colored 3.0.0",
"fern",
@@ -3049,7 +3047,7 @@ dependencies = [
"strum_macros",
"tempfile",
"test-case",
"thiserror 2.0.16",
"thiserror 2.0.12",
"toml 0.9.5",
"typed-arena",
"unicode-normalization",
@@ -3092,7 +3090,7 @@ dependencies = [
"serde_json",
"serde_with",
"test-case",
"thiserror 2.0.16",
"thiserror 2.0.12",
"uuid",
]
@@ -3108,7 +3106,7 @@ name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"aho-corasick",
"bitflags 2.9.3",
"bitflags 2.9.2",
"compact_str",
"get-size2",
"is-macro",
@@ -3123,7 +3121,7 @@ dependencies = [
"salsa",
"schemars",
"serde",
"thiserror 2.0.16",
"thiserror 2.0.12",
]
[[package]]
@@ -3177,7 +3175,7 @@ dependencies = [
"similar",
"smallvec",
"static_assertions",
"thiserror 2.0.16",
"thiserror 2.0.12",
"tracing",
]
@@ -3196,7 +3194,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"itertools 0.14.0",
"ruff_python_ast",
"unic-ucd-category",
@@ -3207,7 +3205,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.2",
"bstr",
"compact_str",
"get-size2",
@@ -3232,7 +3230,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"insta",
"is-macro",
"ruff_cache",
@@ -3253,7 +3251,7 @@ dependencies = [
name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"unicode-ident",
]
@@ -3307,7 +3305,7 @@ dependencies = [
"serde",
"serde_json",
"shellexpand",
"thiserror 2.0.16",
"thiserror 2.0.12",
"toml 0.9.5",
"tracing",
"tracing-log",
@@ -3337,7 +3335,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.12.10"
version = "0.12.9"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3430,11 +3428,11 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -3578,9 +3576,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.143"
version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [
"itoa",
"memchr",
@@ -3791,9 +3789,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.106"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
@@ -3917,11 +3915,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.16"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.16",
"thiserror-impl 2.0.12",
]
[[package]]
@@ -3937,9 +3935,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.16"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@@ -4138,9 +4136,9 @@ dependencies = [
[[package]]
name = "tracing-indicatif"
version = "0.3.13"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04d4e11e0e27acef25a47f27e9435355fecdc488867fa2bc90e75b0700d2823d"
checksum = "e1983afead46ff13a3c93581e0cec31d20b29efdd22cbdaa8b9f850eccf2c352"
dependencies = [
"indicatif",
"tracing",
@@ -4240,22 +4238,17 @@ dependencies = [
name = "ty_ide"
version = "0.0.0"
dependencies = [
"bitflags 2.9.3",
"get-size2",
"bitflags 2.9.2",
"insta",
"itertools 0.14.0",
"rayon",
"regex",
"ruff_db",
"ruff_index",
"ruff_memory_usage",
"ruff_python_ast",
"ruff_python_parser",
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"salsa",
"smallvec",
"tracing",
"ty_project",
@@ -4278,7 +4271,7 @@ dependencies = [
"pep440_rs",
"rayon",
"regex",
"regex-automata 0.4.10",
"regex-automata 0.4.9",
"ruff_cache",
"ruff_db",
"ruff_macros",
@@ -4291,7 +4284,7 @@ dependencies = [
"salsa",
"schemars",
"serde",
"thiserror 2.0.16",
"thiserror 2.0.12",
"toml 0.9.5",
"tracing",
"ty_combine",
@@ -4304,7 +4297,7 @@ name = "ty_python_semantic"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.2",
"bitvec",
"camino",
"colored 3.0.0",
@@ -4344,7 +4337,7 @@ dependencies = [
"strum_macros",
"tempfile",
"test-case",
"thiserror 2.0.16",
"thiserror 2.0.12",
"tracing",
"ty_python_semantic",
"ty_static",
@@ -4357,7 +4350,7 @@ name = "ty_server"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.2",
"crossbeam",
"dunce",
"insta",
@@ -4378,7 +4371,7 @@ dependencies = [
"serde_json",
"shellexpand",
"tempfile",
"thiserror 2.0.16",
"thiserror 2.0.12",
"tracing",
"tracing-subscriber",
"ty_combine",
@@ -4400,7 +4393,7 @@ name = "ty_test"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.3",
"bitflags 2.9.2",
"camino",
"colored 3.0.0",
"insta",
@@ -4419,7 +4412,7 @@ dependencies = [
"serde",
"smallvec",
"tempfile",
"thiserror 2.0.16",
"thiserror 2.0.12",
"toml 0.9.5",
"tracing",
"ty_python_semantic",
@@ -4596,9 +4589,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.7"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
@@ -5150,7 +5143,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.3",
"bitflags 2.9.2",
]
[[package]]

View File

@@ -215,8 +215,6 @@ unexpected_cfgs = { level = "warn", check-cfg = [
[workspace.lints.clippy]
pedantic = { level = "warn", priority = -2 }
# Enabled at the crate level
disallowed_methods = "allow"
# Allowed pedantic lints
char_lit_as_u8 = "allow"
collapsible_else_if = "allow"
@@ -255,7 +253,6 @@ unused_peekable = "warn"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"
[profile.release]
# Note that we set these explicitly, and these values
# were chosen based on a trade-off between compile times

View File

@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.12.10/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.10/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.12.9/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.9/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.10
rev: v0.12.9
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -24,20 +24,3 @@ ignore-interior-mutability = [
# The expression is read-only.
"ruff_python_ast::hashable::HashableExpr",
]
disallowed-methods = [
{ path = "std::env::var", reason = "Use System::env_var instead in ty crates" },
{ path = "std::env::current_dir", reason = "Use System::current_directory instead in ty crates" },
{ path = "std::fs::read_to_string", reason = "Use System::read_to_string instead in ty crates" },
{ path = "std::fs::metadata", reason = "Use System::path_metadata instead in ty crates" },
{ path = "std::fs::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "dunce::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "std::fs::read_dir", reason = "Use System::read_directory instead in ty crates" },
{ path = "std::fs::write", reason = "Use WritableSystem::write_file instead in ty crates" },
{ path = "std::fs::create_dir_all", reason = "Use WritableSystem::create_directory_all instead in ty crates" },
{ path = "std::fs::File::create_new", reason = "Use WritableSystem::create_new_file instead in ty crates" },
# Path methods that have System trait equivalents
{ path = "std::path::Path::exists", reason = "Use System::path_exists instead in ty crates" },
{ path = "std::path::Path::is_dir", reason = "Use System::is_directory instead in ty crates" },
{ path = "std::path::Path::is_file", reason = "Use System::is_file instead in ty crates" },
]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.10"
version = "0.12.9"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -40,7 +40,6 @@ salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
similar = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }

View File

@@ -325,11 +325,6 @@ impl Diagnostic {
self.inner.fix.as_ref()
}
#[cfg(test)]
pub(crate) fn fix_mut(&mut self) -> Option<&mut Fix> {
Arc::make_mut(&mut self.inner).fix.as_mut()
}
/// Set the fix for this diagnostic.
pub fn set_fix(&mut self, fix: Fix) {
debug_assert!(
@@ -1299,10 +1294,6 @@ pub struct DisplayDiagnosticConfig {
hide_severity: bool,
/// Whether to show the availability of a fix in a diagnostic.
show_fix_status: bool,
/// Whether to show the diff for an available fix after the main diagnostic.
///
/// This currently only applies to `DiagnosticFormat::Full`.
show_fix_diff: bool,
/// The lowest applicability that should be shown when reporting diagnostics.
fix_applicability: Applicability,
}
@@ -1350,14 +1341,6 @@ impl DisplayDiagnosticConfig {
}
}
/// Whether to show a diff for an available fix after the main diagnostic.
pub fn show_fix_diff(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
show_fix_diff: yes,
..self
}
}
/// Set the lowest fix applicability that should be shown.
///
/// In other words, an applicability of `Safe` (the default) would suppress showing fixes or fix
@@ -1381,7 +1364,6 @@ impl Default for DisplayDiagnosticConfig {
preview: false,
hide_severity: false,
show_fix_status: false,
show_fix_diff: false,
fix_applicability: Applicability::Safe,
}
}

View File

@@ -2622,13 +2622,6 @@ watermelon
self.config = config;
}
/// Show a diff for the fix when rendering.
pub(super) fn show_fix_diff(&mut self, yes: bool) {
let mut config = std::mem::take(&mut self.config);
config = config.show_fix_diff(yes);
self.config = config;
}
/// The lowest fix applicability to show when rendering.
pub(super) fn fix_applicability(&mut self, applicability: Applicability) {
let mut config = std::mem::take(&mut self.config);

View File

@@ -1,18 +1,7 @@
use std::borrow::Cow;
use std::num::NonZeroUsize;
use anstyle::Style;
use ruff_notebook::NotebookIndex;
use similar::{ChangeTag, TextDiff};
use ruff_annotate_snippets::Renderer as AnnotateRenderer;
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::OneIndexed;
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::diagnostic::render::{FileResolver, Resolved};
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
use crate::diagnostic::{Diagnostic, DisplayDiagnosticConfig, stylesheet::DiagnosticStylesheet};
pub(super) struct FullRenderer<'a> {
resolver: &'a dyn FileResolver,
@@ -59,245 +48,15 @@ impl<'a> FullRenderer<'a> {
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
}
writeln!(f)?;
if self.config.show_fix_diff {
if let Some(diff) = Diff::from_diagnostic(diag, &stylesheet, self.resolver) {
writeln!(f, "{diff}")?;
}
}
}
Ok(())
}
}
/// Renders a diff that shows the code fixes.
///
/// The implementation isn't fully fledged out and only used by tests. Before using in production, try
/// * Improve layout
/// * Replace tabs with spaces for a consistent experience across terminals
/// * Replace zero-width whitespaces
/// * Print a simpler diff if only a single line has changed
/// * Compute the diff from the `Edit` because diff calculation is expensive.
struct Diff<'a> {
fix: &'a Fix,
diagnostic_source: DiagnosticSource,
notebook_index: Option<NotebookIndex>,
stylesheet: &'a DiagnosticStylesheet,
}
impl<'a> Diff<'a> {
fn from_diagnostic(
diagnostic: &'a Diagnostic,
stylesheet: &'a DiagnosticStylesheet,
resolver: &'a dyn FileResolver,
) -> Option<Diff<'a>> {
let file = &diagnostic.primary_span_ref()?.file;
Some(Diff {
fix: diagnostic.fix()?,
diagnostic_source: file.diagnostic_source(resolver),
notebook_index: resolver.notebook_index(file),
stylesheet,
})
}
}
impl std::fmt::Display for Diff<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let source_code = self.diagnostic_source.as_source_code();
let source_text = source_code.text();
// Partition the source code into end offsets for each cell. If `self.notebook_index` is
// `None`, indicating a regular script file, all the lines will be in one "cell" under the
// `None` key.
let cells = if let Some(notebook_index) = &self.notebook_index {
let mut last_cell = OneIndexed::MIN;
let mut cells: Vec<(Option<OneIndexed>, TextSize)> = Vec::new();
for (row, cell) in notebook_index.iter() {
if cell != last_cell {
let offset = source_code.line_start(row);
cells.push((Some(last_cell), offset));
last_cell = cell;
}
}
cells.push((Some(last_cell), source_text.text_len()));
cells
} else {
vec![(None, source_text.text_len())]
};
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Safe fix",
Applicability::Unsafe => "Unsafe fix",
Applicability::DisplayOnly => "Display-only fix",
};
// TODO(brent) `stylesheet.separator` is cyan rather than blue, as we had before. I think
// we're getting rid of this soon anyway, so I didn't think it was worth adding another
// style to the stylesheet temporarily. The color doesn't appear at all in the snapshot
// tests, which is the only place these are currently used.
writeln!(f, " {}", fmt_styled(message, self.stylesheet.separator))?;
let mut last_end = TextSize::ZERO;
for (cell, offset) in cells {
let range = TextRange::new(last_end, offset);
last_end = offset;
let input = source_code.slice(range);
let mut output = String::with_capacity(input.len());
let mut last_end = range.start();
let mut applied = 0;
for edit in self.fix.edits() {
if range.contains_range(edit.range()) {
output.push_str(source_code.slice(TextRange::new(last_end, edit.start())));
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
applied += 1;
}
}
// No edits were applied, so there's no need to diff.
if applied == 0 {
continue;
}
output.push_str(&source_text[usize::from(last_end)..usize::from(range.end())]);
let diff = TextDiff::from_lines(input, &output);
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let digit_with = OneIndexed::from_zero_indexed(largest_new.max(largest_old)).digits();
if let Some(cell) = cell {
// Room for 2 digits, 2 x 1 space before each digit, 1 space, and 1 `|`. This
// centers the three colons on the pipe.
writeln!(f, "{:>1$} cell {cell}", ":::", 2 * digit_with.get() + 4)?;
}
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag(), self.stylesheet);
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with,
},
Line {
index: new_index,
width: digit_with,
},
fmt_styled(line_style.apply_to(sign), self.stylesheet.emphasis),
)?;
for (emphasized, value) in change.iter_strings_lossy() {
let value = show_nonprinting(&value);
if emphasized {
write!(
f,
"{}",
fmt_styled(
line_style.apply_to(&value),
self.stylesheet.underline
)
)?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
}
}
}
Ok(())
}
}
struct LineStyle {
style: Style,
}
impl LineStyle {
fn apply_to(&self, input: &str) -> impl std::fmt::Display {
fmt_styled(input, self.style)
}
fn from(value: ChangeTag, stylesheet: &DiagnosticStylesheet) -> LineStyle {
match value {
ChangeTag::Equal => LineStyle {
style: stylesheet.none,
},
ChangeTag::Delete => LineStyle {
style: stylesheet.deletion,
},
ChangeTag::Insert => LineStyle {
style: stylesheet.insertion,
},
}
}
}
struct Line {
index: Option<OneIndexed>,
width: NonZeroUsize,
}
impl std::fmt::Display for Line {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.index {
None => {
for _ in 0..self.width.get() {
f.write_str(" ")?;
}
Ok(())
}
Some(idx) => write!(f, "{:<width$}", idx, width = self.width.get()),
}
}
}
fn show_nonprinting(s: &str) -> Cow<'_, str> {
if s.find(['\x07', '\x08', '\x1b', '\x7f']).is_some() {
Cow::Owned(
s.replace('\x07', "")
.replace('\x08', "")
.replace('\x1b', "")
.replace('\x7f', ""),
)
} else {
Cow::Borrowed(s)
}
}
#[cfg(test)]
mod tests {
use ruff_diagnostics::{Applicability, Fix};
use ruff_diagnostics::Applicability;
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::diagnostic::{
@@ -698,107 +457,6 @@ print()
");
}
/// Test that we remap notebook cell line numbers in the diff as well as the main diagnostic.
#[test]
fn notebook_output_with_diff() {
let (mut env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
env.show_fix_diff(true);
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
error[unused-import][*]: `os` imported but unused
--> notebook.ipynb:cell 1:2:8
|
1 | # cell 1
2 | import os
| ^^
|
help: Remove unused import: `os`
Safe fix
::: cell 1
1 1 | # cell 1
2 |-import os
error[unused-import][*]: `math` imported but unused
--> notebook.ipynb:cell 2:2:8
|
1 | # cell 2
2 | import math
| ^^^^
3 |
4 | print('hello world')
|
help: Remove unused import: `math`
Safe fix
::: cell 2
1 1 | # cell 2
2 |-import math
3 2 |
4 3 | print('hello world')
error[unused-variable]: Local variable `x` is assigned to but never used
--> notebook.ipynb:cell 3:4:5
|
2 | def foo():
3 | print()
4 | x = 1
| ^
|
help: Remove assignment to unused variable `x`
Unsafe fix
::: cell 3
1 1 | # cell 3
2 2 | def foo():
3 3 | print()
4 |- x = 1
5 4 |
");
}
#[test]
fn notebook_output_with_diff_spanning_cells() {
let (mut env, mut diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
env.show_fix_diff(true);
// Move all of the edits from the later diagnostics to the first diagnostic to simulate a
// single diagnostic with edits in different cells.
let mut diagnostic = diagnostics.swap_remove(0);
let fix = diagnostic.fix_mut().unwrap();
let mut edits = fix.edits().to_vec();
for diag in diagnostics {
edits.extend_from_slice(diag.fix().unwrap().edits());
}
*fix = Fix::unsafe_edits(edits.remove(0), edits);
insta::assert_snapshot!(env.render(&diagnostic), @r"
error[unused-import]: `os` imported but unused
--> notebook.ipynb:cell 1:2:8
|
1 | # cell 1
2 | import os
| ^^
|
help: Remove unused import: `os`
Unsafe fix
::: cell 1
1 1 | # cell 1
2 |-import os
::: cell 2
1 1 | # cell 2
2 |-import math
3 2 |
4 3 | print('hello world')
::: cell 3
1 1 | # cell 3
2 2 | def foo():
3 3 | print()
4 |- x = 1
5 4 |
");
}
/// Carriage return (`\r`) is a valid line-ending in Python, so we should normalize this to a
/// line feed (`\n`) for rendering. Otherwise we report a single long line for this case.
#[test]

View File

@@ -40,12 +40,9 @@ pub struct DiagnosticStylesheet {
pub(crate) help: Style,
pub(crate) line_no: Style,
pub(crate) emphasis: Style,
pub(crate) underline: Style,
pub(crate) none: Style,
pub(crate) separator: Style,
pub(crate) secondary_code: Style,
pub(crate) insertion: Style,
pub(crate) deletion: Style,
}
impl Default for DiagnosticStylesheet {
@@ -66,12 +63,9 @@ impl DiagnosticStylesheet {
help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
line_no: bright_blue.effects(Effects::BOLD),
emphasis: Style::new().effects(Effects::BOLD),
underline: Style::new().effects(Effects::UNDERLINE),
none: Style::new(),
separator: AnsiColor::Cyan.on_default(),
secondary_code: AnsiColor::Red.on_default().effects(Effects::BOLD),
insertion: AnsiColor::Green.on_default(),
deletion: AnsiColor::Red.on_default(),
}
}
@@ -84,12 +78,9 @@ impl DiagnosticStylesheet {
help: Style::new(),
line_no: Style::new(),
emphasis: Style::new(),
underline: Style::new(),
none: Style::new(),
separator: Style::new(),
secondary_code: Style::new(),
insertion: Style::new(),
deletion: Style::new(),
}
}
}

View File

@@ -1,8 +1,3 @@
#![warn(
clippy::disallowed_methods,
reason = "Prefer System trait methods over std methods"
)]
use crate::files::Files;
use crate::system::System;
use crate::vendored::VendoredFileSystem;
@@ -70,10 +65,6 @@ pub trait Db: salsa::Database {
/// to process work in parallel. For example, to index a directory or checking the files of a project.
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
/// watching the files for changes.
#[expect(
clippy::disallowed_methods,
reason = "We don't have access to System here, but this is also only used by the CLI and the server which always run on a real system."
)]
pub fn max_parallelism() -> NonZeroUsize {
std::env::var(EnvVars::TY_MAX_PARALLELISM)
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))

View File

@@ -92,14 +92,14 @@ impl ParsedModule {
self.inner.store(None);
}
/// Returns the pointer address of this [`ParsedModule`].
/// Returns a pointer for this [`ParsedModule`].
///
/// The pointer uniquely identifies the module within the current Salsa revision,
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
pub fn addr(&self) -> usize {
pub fn as_ptr(&self) -> *const () {
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
// `Arc` within the `ArcSwap` may change.
Arc::as_ptr(&self.inner).addr()
Arc::as_ptr(&self.inner).cast()
}
}
@@ -202,13 +202,9 @@ mod indexed {
/// Returns the node at the given index.
pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> {
let index = index
.as_u32()
.expect("attempted to access uninitialized `NodeIndex`");
// Note that this method restores the correct lifetime: the nodes are valid for as
// long as the reference to `IndexedModule` is alive.
self.index[index as usize]
self.index[index.as_usize()]
}
}
@@ -224,7 +220,7 @@ mod indexed {
T: HasNodeIndex + std::fmt::Debug,
AnyRootNodeRef<'a>: From<&'a T>,
{
node.node_index().set(NodeIndex::from(self.index));
node.node_index().set(self.index);
self.nodes.push(AnyRootNodeRef::from(node));
self.index += 1;
}

View File

@@ -46,7 +46,7 @@ pub type Result<T> = std::io::Result<T>;
/// * File watching isn't supported.
///
/// Abstracting the system also enables tests to use a more efficient in-memory file system.
pub trait System: Debug + Sync + Send {
pub trait System: Debug {
/// Reads the metadata of the file or directory at `path`.
///
/// This function will traverse symbolic links to query information about the destination file.
@@ -197,8 +197,6 @@ pub trait System: Debug + Sync + Send {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn dyn_clone(&self) -> Box<dyn System>;
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]

View File

@@ -1,5 +1,3 @@
#![allow(clippy::disallowed_methods)]
use super::walk_directory::{
self, DirectoryWalker, WalkDirectoryBuilder, WalkDirectoryConfiguration,
WalkDirectoryVisitorBuilder, WalkState,
@@ -257,10 +255,6 @@ impl System for OsSystem {
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
std::env::var(name)
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl OsSystem {

View File

@@ -146,10 +146,6 @@ impl System for TestSystem {
fn case_sensitivity(&self) -> CaseSensitivity {
self.system().case_sensitivity()
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl Default for TestSystem {
@@ -398,13 +394,6 @@ impl System for InMemorySystem {
fn case_sensitivity(&self) -> CaseSensitivity {
CaseSensitivity::CaseSensitive
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(Self {
user_config_directory: Mutex::new(self.user_config_directory.lock().unwrap().clone()),
memory_fs: self.memory_fs.clone(),
})
}
}
impl WritableSystem for InMemorySystem {

View File

@@ -13,7 +13,6 @@ license = { workspace = true }
[dependencies]
ty = { workspace = true }
ty_project = { workspace = true, features = ["schemars"] }
ty_python_semantic = { workspace = true }
ty_static = { workspace = true }
ruff = { workspace = true }
ruff_formatter = { workspace = true }

View File

@@ -52,7 +52,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
}
fn generate_markdown() -> String {
let registry = ty_python_semantic::default_lint_registry();
let registry = &*ty_project::DEFAULT_LINT_REGISTRY;
let mut output = String::new();

View File

@@ -14,11 +14,8 @@ license = { workspace = true }
doctest = false
[dependencies]
ruff_text_size = { workspace = true, features = ["get-size"] }
ruff_text_size = { workspace = true }
get-size2 = { workspace = true }
is-macro = { workspace = true }
serde = { workspace = true, optional = true, features = [] }
[features]
serde = ["dep:serde", "ruff_text_size/serde"]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.12.10"
version = "0.12.9"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -34,7 +34,7 @@ task_group()
setup()
from airflow.decorators import teardown
from airflow.io.path import ObjectStoragePath
from airflow.io.store import attach
from airflow.io.storage import attach
from airflow.models import DAG as DAGFromModel
from airflow.models import (
Connection,
@@ -74,36 +74,3 @@ DatasetOrTimeSchedule()
# airflow.utils.dag_parsing_context
get_parsing_context()
from airflow.decorators.base import (
DecoratedMappedOperator,
DecoratedOperator,
TaskDecorator,
get_unique_task_id,
task_decorator_factory,
)
# airflow.decorators.base
DecoratedMappedOperator()
DecoratedOperator()
TaskDecorator()
get_unique_task_id()
task_decorator_factory()
from airflow.models import Param
# airflow.models
Param()
from airflow.sensors.base import (
BaseSensorOperator,
PokeReturnValue,
poke_mode_only,
)
# airflow.sensors.base
BaseSensorOperator()
PokeReturnValue()
poke_mode_only()

View File

@@ -9,6 +9,7 @@ from airflow.operators.empty import EmptyOperator
from airflow.operators.latest_only import LatestOnlyOperator
from airflow.operators.trigger_dagrun import TriggerDagRunOperator
from airflow.operators.weekday import BranchDayOfWeekOperator
from airflow.sensors.date_time import DateTimeSensor
FSHook()
PackageIndexHook()
@@ -21,6 +22,7 @@ EmptyOperator()
LatestOnlyOperator()
BranchDayOfWeekOperator()
DateTimeSensor()
from airflow.operators.python import (
BranchPythonOperator,
@@ -28,23 +30,16 @@ from airflow.operators.python import (
PythonVirtualenvOperator,
ShortCircuitOperator,
)
from airflow.sensors.bash import BashSensor
from airflow.sensors.date_time import DateTimeSensor
BranchPythonOperator()
PythonOperator()
PythonVirtualenvOperator()
ShortCircuitOperator()
BashSensor()
DateTimeSensor()
from airflow.sensors.date_time import DateTimeSensorAsync
from airflow.sensors.external_task import (
ExternalTaskMarker,
ExternalTaskSensor,
)
from airflow.sensors.time_sensor import (
TimeSensor,
TimeSensorAsync,
)
from airflow.sensors.filesystem import FileSensor
from airflow.sensors.python import PythonSensor
BranchPythonOperator()
PythonOperator()
@@ -54,13 +49,6 @@ DateTimeSensorAsync()
ExternalTaskMarker()
ExternalTaskSensor()
FileSensor()
PythonSensor()
from airflow.sensors.time_sensor import (
TimeSensor,
TimeSensorAsync,
)
TimeSensor()
TimeSensorAsync()

View File

@@ -1,75 +0,0 @@
from typing import Optional
import httpx
def foo():
client = httpx.Client()
client.close() # Ok
client.delete() # Ok
client.get() # Ok
client.head() # Ok
client.options() # Ok
client.patch() # Ok
client.post() # Ok
client.put() # Ok
client.request() # Ok
client.send() # Ok
client.stream() # Ok
client.anything() # Ok
client.build_request() # Ok
client.is_closed # Ok
async def foo():
client = httpx.Client()
client.close() # ASYNC212
client.delete() # ASYNC212
client.get() # ASYNC212
client.head() # ASYNC212
client.options() # ASYNC212
client.patch() # ASYNC212
client.post() # ASYNC212
client.put() # ASYNC212
client.request() # ASYNC212
client.send() # ASYNC212
client.stream() # ASYNC212
client.anything() # Ok
client.build_request() # Ok
client.is_closed # Ok
async def foo(client: httpx.Client):
client.request() # ASYNC212
client.anything() # Ok
async def foo(client: httpx.Client | None):
client.request() # ASYNC212
client.anything() # Ok
async def foo(client: Optional[httpx.Client]):
client.request() # ASYNC212
client.anything() # Ok
async def foo():
client: httpx.Client = ...
client.request() # ASYNC212
client.anything() # Ok
global_client = httpx.Client()
async def foo():
global_client.request() # ASYNC212
global_client.anything() # Ok
async def foo():
async with httpx.AsyncClient() as client:
await client.get() # Ok

View File

@@ -17,50 +17,3 @@ info(f"{__name__}")
# Don't trigger for t-strings
info(t"{name}")
info(t"{__name__}")
count = 5
total = 9
directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
logging.info(f"{count} out of {total} files in {directory_path} checked")
x = 99
fmt = "08d"
logger.info(f"{x:{'08d'}}")
logger.info(f"{x:>10} {x:{fmt}}")
logging.info(f"")
logging.info(f"This message doesn't have any variables.")
obj = {"key": "value"}
logging.info(f"Object: {obj!r}")
items_count = 3
logging.warning(f"Items: {items_count:d}")
data = {"status": "active"}
logging.info(f"Processing {len(data)} items")
logging.info(f"Status: {data.get('status', 'unknown').upper()}")
result = 123
logging.info(f"Calculated result: {result + 100}")
temperature = 123
logging.info(f"Temperature: {temperature:.1f}°C")
class FilePath:
def __init__(self, name: str):
self.name = name
logging.info(f"No changes made to {file_path.name}.")
user = "tron"
balance = 123.45
logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
import logging
x = 1
logging.error(f"{x} -> %s", x)

View File

@@ -1,10 +0,0 @@
"""Test f-string argument order."""
import logging
logger = logging.getLogger(__name__)
X = 1
Y = 2
logger.error(f"{X} -> %s", Y)
logger.error(f"{Y} -> %s", X)

View File

@@ -13,11 +13,3 @@ Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
fd = os.open(".", os.O_RDONLY)
os.symlink("source.txt", "link.txt", dir_fd=fd) # Ok: dir_fd is not supported by pathlib
os.close(fd)
os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")

View File

@@ -106,22 +106,4 @@ os.replace("src", "dst", src_dir_fd=1)
os.replace("src", "dst", dst_dir_fd=2)
os.getcwd()
os.getcwdb()
os.mkdir(path="directory")
os.mkdir(
# comment 1
"directory",
mode=0o777
)
os.mkdir("directory", mode=0o777, dir_fd=1)
os.makedirs("name", 0o777, exist_ok=False)
os.makedirs("name", 0o777, False)
os.makedirs(name="name", mode=0o777, exist_ok=False)
os.makedirs("name", unknown_kwarg=True)
os.getcwdb()

View File

@@ -151,39 +151,3 @@ def f():
pass
except Exception as _:
pass
# OK, `__class__` in this case is not the special `__class__` cell, so we don't
# emit a diagnostic. (It has its own special semantics -- see
# https://github.com/astral-sh/ruff/pull/20048#discussion_r2298338048 -- but
# those aren't relevant here.)
class A:
__class__ = 1
# The following three cases are flagged because they declare local `__class__`
# variables that don't refer to the special `__class__` cell.
class A:
def set_class(self, cls):
__class__ = cls # F841
class A:
class B:
def set_class(self, cls):
__class__ = cls # F841
class A:
def foo():
class B:
print(__class__)
def set_class(self, cls):
__class__ = cls # F841
# OK, the `__class__` cell is nonlocal and declared as such.
class NonlocalDunderClass:
def foo():
nonlocal __class__
__class__ = 1

View File

@@ -44,8 +44,3 @@ def f():
def g():
nonlocal x
x = 2
# OK
class A:
def method(self):
nonlocal __class__

View File

@@ -1,18 +0,0 @@
from __future__ import nested_scopes, generators
from __future__ import with_statement, unicode_literals
from __future__ import absolute_import, division
from __future__ import generator_stop
from __future__ import print_function, nested_scopes, generator_stop
print(with_statement)
generators = 1
class Foo():
def boo(self):
print(division)
__all__ = ["print_function", "generator_stop"]

View File

@@ -118,10 +118,3 @@ def func():
return lambda: value
defaultdict(constant_factory("<missing>"))
def func():
defaultdict(default_factory=t"") # OK
def func():
defaultdict(default_factory=t"hello") # OK

View File

@@ -124,19 +124,3 @@ def fun_with_python_syntax():
...
return Foo
@dataclass
class C:
def __post_init__(self, x: tuple[int, ...] = (
1,
2,
)) -> None:
self.x = x
@dataclass
class D:
def __post_init__(self, x: int = """
""") -> None:
self.x = x

View File

@@ -102,8 +102,3 @@ deque("abc") # OK
deque(b"abc") # OK
deque(f"" "a") # OK
deque(f"{x}" "") # OK
# https://github.com/astral-sh/ruff/issues/19951
deque(t"")
deque(t"" t"")
deque(t"{""}") # OK

View File

@@ -1,11 +1,10 @@
use ruff_python_ast::PythonVersion;
use ruff_python_semantic::{Binding, ScopeKind};
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{
flake8_builtins, flake8_pyi, flake8_type_checking, flake8_unused_arguments, pep8_naming,
pyflakes, pylint, pyupgrade, ruff,
pyflakes, pylint, ruff,
};
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
@@ -46,7 +45,6 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
Rule::UnusedStaticMethodArgument,
Rule::UnusedUnpackedVariable,
Rule::UnusedVariable,
Rule::UnnecessaryFutureImport,
]) {
return;
}
@@ -226,11 +224,6 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
if checker.is_rule_enabled(Rule::UnusedImport) {
pyflakes::rules::unused_import(checker, scope);
}
if checker.is_rule_enabled(Rule::UnnecessaryFutureImport) {
if checker.target_version() >= PythonVersion::PY37 {
pyupgrade::rules::unnecessary_future_import(checker, scope);
}
}
if checker.is_rule_enabled(Rule::ImportPrivateName) {
pylint::rules::import_private_name(checker, scope);

View File

@@ -660,9 +660,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::BlockingHttpCallInAsyncFunction) {
flake8_async::rules::blocking_http_call(checker, call);
}
if checker.is_rule_enabled(Rule::BlockingHttpCallHttpxInAsyncFunction) {
flake8_async::rules::blocking_http_call_httpx(checker, call);
}
if checker.is_rule_enabled(Rule::BlockingOpenCallInAsyncFunction) {
flake8_async::rules::blocking_open_call(checker, call);
}
@@ -1042,6 +1039,8 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
}
if checker.any_rule_enabled(&[
Rule::OsMkdir,
Rule::OsMakedirs,
Rule::OsStat,
Rule::OsPathJoin,
Rule::OsPathSplitext,
@@ -1049,6 +1048,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
Rule::PyPath,
Rule::Glob,
Rule::OsListdir,
Rule::OsSymlink,
]) {
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
}
@@ -1120,15 +1120,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::OsPathSamefile) {
flake8_use_pathlib::rules::os_path_samefile(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMkdir) {
flake8_use_pathlib::rules::os_mkdir(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMakedirs) {
flake8_use_pathlib::rules::os_makedirs(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsSymlink) {
flake8_use_pathlib::rules::os_symlink(checker, call, segments);
}
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
flake8_use_pathlib::rules::path_constructor_current_directory(
checker, call, segments,

View File

@@ -728,6 +728,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pylint::rules::non_ascii_module_import(checker, alias);
}
}
if checker.is_rule_enabled(Rule::UnnecessaryFutureImport) {
if checker.target_version() >= PythonVersion::PY37 {
if let Some("__future__") = module {
pyupgrade::rules::unnecessary_future_import(checker, stmt, names);
}
}
}
if checker.is_rule_enabled(Rule::DeprecatedMockImport) {
pyupgrade::rules::deprecated_mock_import(checker, stmt);
}

View File

@@ -703,10 +703,7 @@ impl SemanticSyntaxContext for Checker<'_> {
match scope.kind {
ScopeKind::Class(_) | ScopeKind::Lambda(_) => return false,
ScopeKind::Function(ast::StmtFunctionDef { is_async, .. }) => return *is_async,
ScopeKind::Generator { .. }
| ScopeKind::Module
| ScopeKind::Type
| ScopeKind::DunderClassCell => {}
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
}
}
false
@@ -717,10 +714,7 @@ impl SemanticSyntaxContext for Checker<'_> {
match scope.kind {
ScopeKind::Class(_) => return false,
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
ScopeKind::Generator { .. }
| ScopeKind::Module
| ScopeKind::Type
| ScopeKind::DunderClassCell => {}
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
}
}
false
@@ -731,7 +725,7 @@ impl SemanticSyntaxContext for Checker<'_> {
match scope.kind {
ScopeKind::Class(_) | ScopeKind::Generator { .. } => return false,
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
ScopeKind::Module | ScopeKind::Type | ScopeKind::DunderClassCell => {}
ScopeKind::Module | ScopeKind::Type => {}
}
}
false
@@ -1098,24 +1092,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
}
}
// Here we add the implicit scope surrounding a method which allows code in the
// method to access `__class__` at runtime. See the `ScopeKind::DunderClassCell`
// docs for more information.
let added_dunder_class_scope = if self.semantic.current_scope().kind.is_class() {
self.semantic.push_scope(ScopeKind::DunderClassCell);
let binding_id = self.semantic.push_binding(
TextRange::default(),
BindingKind::DunderClassCell,
BindingFlags::empty(),
);
self.semantic
.current_scope_mut()
.add("__class__", binding_id);
true
} else {
false
};
self.semantic.push_scope(ScopeKind::Type);
if let Some(type_params) = type_params {
@@ -1179,9 +1155,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
self.semantic.pop_scope(); // Function scope
self.semantic.pop_definition();
self.semantic.pop_scope(); // Type parameter scope
if added_dunder_class_scope {
self.semantic.pop_scope(); // `__class__` cell closure scope
}
self.add_binding(
name,
stmt.identifier(),

View File

@@ -336,7 +336,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncZeroSleep),
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::LongSleepNotForever),
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
(Flake8Async, "212") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingHttpCallHttpxInAsyncFunction),
(Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction),
(Flake8Async, "221") => (RuleGroup::Stable, rules::flake8_async::rules::RunProcessInAsyncFunction),
(Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction),
@@ -922,8 +921,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// flake8-use-pathlib
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsChmod),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMakedirs),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRename),
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReplace),
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),
@@ -955,7 +954,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob),
(Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir),
(Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::OsSymlink),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::violations::OsSymlink),
// flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),

View File

@@ -0,0 +1,202 @@
use std::fmt::{Display, Formatter};
use std::num::NonZeroUsize;
use colored::{Color, ColoredString, Colorize, Styles};
use similar::{ChangeTag, TextDiff};
use ruff_db::diagnostic::Diagnostic;
use ruff_source_file::{OneIndexed, SourceFile};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::text_helpers::ShowNonprinting;
use crate::{Applicability, Fix};
/// Renders a diff that shows the code fixes.
///
/// The implementation isn't fully fledged out and only used by tests. Before using in production, try
/// * Improve layout
/// * Replace tabs with spaces for a consistent experience across terminals
/// * Replace zero-width whitespaces
/// * Print a simpler diff if only a single line has changed
/// * Compute the diff from the [`Edit`] because diff calculation is expensive.
pub(super) struct Diff<'a> {
fix: &'a Fix,
source_code: &'a SourceFile,
}
impl<'a> Diff<'a> {
pub(crate) fn from_message(message: &'a Diagnostic) -> Option<Diff<'a>> {
message.fix().map(|fix| Diff {
source_code: message.expect_ruff_source_file(),
fix,
})
}
}
impl Display for Diff<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
let mut output = String::with_capacity(self.source_code.source_text().len());
let mut last_end = TextSize::default();
for edit in self.fix.edits() {
output.push_str(
self.source_code
.slice(TextRange::new(last_end, edit.start())),
);
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
}
output.push_str(&self.source_code.source_text()[usize::from(last_end)..]);
let diff = TextDiff::from_lines(self.source_code.source_text(), &output);
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Safe fix",
Applicability::Unsafe => "Unsafe fix",
Applicability::DisplayOnly => "Display-only fix",
};
writeln!(f, " {}", message.blue())?;
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let digit_with =
calculate_print_width(OneIndexed::from_zero_indexed(largest_new.max(largest_old)));
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag());
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with
},
Line {
index: new_index,
width: digit_with
},
line_style.apply_to(sign).bold()
)?;
for (emphasized, value) in change.iter_strings_lossy() {
let value = value.show_nonprinting();
if emphasized {
write!(f, "{}", line_style.apply_to(&value).underline().on_black())?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
}
}
Ok(())
}
}
struct LineStyle {
fgcolor: Option<Color>,
style: Option<Styles>,
}
impl LineStyle {
fn apply_to(&self, input: &str) -> ColoredString {
let mut colored = ColoredString::from(input);
if let Some(color) = self.fgcolor {
colored = colored.color(color);
}
if let Some(style) = self.style {
match style {
Styles::Clear => colored.clear(),
Styles::Bold => colored.bold(),
Styles::Dimmed => colored.dimmed(),
Styles::Underline => colored.underline(),
Styles::Reversed => colored.reversed(),
Styles::Italic => colored.italic(),
Styles::Blink => colored.blink(),
Styles::Hidden => colored.hidden(),
Styles::Strikethrough => colored.strikethrough(),
}
} else {
colored
}
}
}
impl From<ChangeTag> for LineStyle {
fn from(value: ChangeTag) -> Self {
match value {
ChangeTag::Equal => LineStyle {
fgcolor: None,
style: Some(Styles::Dimmed),
},
ChangeTag::Delete => LineStyle {
fgcolor: Some(Color::Red),
style: None,
},
ChangeTag::Insert => LineStyle {
fgcolor: Some(Color::Green),
style: None,
},
}
}
}
struct Line {
index: Option<OneIndexed>,
width: NonZeroUsize,
}
impl Display for Line {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self.index {
None => {
for _ in 0..self.width.get() {
f.write_str(" ")?;
}
Ok(())
}
Some(idx) => write!(f, "{:<width$}", idx, width = self.width.get()),
}
}
}
/// Calculate the length of the string representation of `value`
pub(super) fn calculate_print_width(mut value: OneIndexed) -> NonZeroUsize {
const TEN: OneIndexed = OneIndexed::from_zero_indexed(9);
let mut width = OneIndexed::ONE;
while value >= TEN {
value = OneIndexed::new(value.get() / 10).unwrap_or(OneIndexed::MIN);
width = width.checked_add(1).unwrap();
}
width
}

View File

@@ -10,6 +10,7 @@ use ruff_notebook::NotebookIndex;
use ruff_source_file::{LineColumn, OneIndexed};
use crate::fs::relativize_path;
use crate::message::diff::calculate_print_width;
use crate::message::{Emitter, EmitterContext};
use crate::settings::types::UnsafeFixes;
@@ -52,8 +53,8 @@ impl Emitter for GroupedEmitter {
max_column_length = max_column_length.max(message.start_location.column);
}
let row_length = max_row_length.digits();
let column_length = max_column_length.digits();
let row_length = calculate_print_width(max_row_length);
let column_length = calculate_print_width(max_column_length);
// Print the filename.
writeln!(writer, "{}:", relativize_path(&*filename).underline())?;
@@ -130,7 +131,8 @@ impl Display for DisplayGroupedMessage<'_> {
write!(
f,
" {row_padding}",
row_padding = " ".repeat(self.row_length.get() - start_location.line.digits().get())
row_padding = " "
.repeat(self.row_length.get() - calculate_print_width(start_location.line).get())
)?;
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
@@ -157,8 +159,9 @@ impl Display for DisplayGroupedMessage<'_> {
f,
"{row}{sep}{col}{col_padding} {code_and_body}",
sep = ":".cyan(),
col_padding =
" ".repeat(self.column_length.get() - start_location.column.digits().get()),
col_padding = " ".repeat(
self.column_length.get() - calculate_print_width(start_location.column).get()
),
code_and_body = RuleCodeAndBody {
message,
show_fix_status: self.show_fix_status,

View File

@@ -21,6 +21,7 @@ pub use text::TextEmitter;
use crate::Fix;
use crate::registry::Rule;
mod diff;
mod github;
mod gitlab;
mod grouped;

View File

@@ -1,19 +1,23 @@
use std::io::Write;
use ruff_db::diagnostic::{
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
};
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig};
use crate::message::diff::Diff;
use crate::message::{Emitter, EmitterContext};
use crate::settings::types::UnsafeFixes;
pub struct TextEmitter {
/// Whether to show the diff of a fix, for diagnostics that have a fix.
///
/// Note that this is not currently exposed in the CLI (#7352) and is only used in tests.
show_fix_diff: bool,
config: DisplayDiagnosticConfig,
}
impl Default for TextEmitter {
fn default() -> Self {
Self {
show_fix_diff: false,
config: DisplayDiagnosticConfig::default()
.format(DiagnosticFormat::Concise)
.hide_severity(true)
@@ -31,7 +35,7 @@ impl TextEmitter {
#[must_use]
pub fn with_show_fix_diff(mut self, show_fix_diff: bool) -> Self {
self.config = self.config.show_fix_diff(show_fix_diff);
self.show_fix_diff = show_fix_diff;
self
}
@@ -73,11 +77,15 @@ impl Emitter for TextEmitter {
diagnostics: &[Diagnostic],
context: &EmitterContext,
) -> anyhow::Result<()> {
write!(
writer,
"{}",
DisplayDiagnostics::new(context, &self.config, diagnostics)
)?;
for message in diagnostics {
write!(writer, "{}", message.display(context, &self.config))?;
if self.show_fix_diff {
if let Some(diff) = Diff::from_message(message) {
writeln!(writer, "{diff}")?;
}
}
}
Ok(())
}

View File

@@ -40,11 +40,6 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet
settings.preview.is_enabled()
}
/// <https://github.com/astral-sh/ruff/pull/19303>
pub(crate) const fn is_fix_f_string_logging_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/16719
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
@@ -164,21 +159,6 @@ pub(crate) const fn is_fix_os_getcwd_enabled(settings: &LinterSettings) -> bool
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_mkdir_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_makedirs_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/20009
pub(crate) const fn is_fix_os_symlink_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/11436
// https://github.com/astral-sh/ruff/pull/11168
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {

View File

@@ -354,10 +354,7 @@ impl Renamer {
))
}
// Avoid renaming builtins and other "special" bindings.
BindingKind::FutureImport
| BindingKind::Builtin
| BindingKind::Export(_)
| BindingKind::DunderClassCell => None,
BindingKind::FutureImport | BindingKind::Builtin | BindingKind::Export(_) => None,
// By default, replace the binding's name with the target name.
BindingKind::Annotation
| BindingKind::Argument

View File

@@ -215,12 +215,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
version: "0.0.1",
}
}
["airflow", "sensors", "bash", "BashSensor"] => ProviderReplacement::AutoImport {
module: "airflow.providers.standard.sensor.bash",
name: "BashSensor",
provider: "standard",
version: "0.0.1",
},
[
"airflow",
"sensors",
@@ -249,12 +243,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
provider: "standard",
version: "0.0.2",
},
["airflow", "sensors", "python", "PythonSensor"] => ProviderReplacement::AutoImport {
module: "airflow.providers.standard.sensors.python",
name: "PythonSensor",
provider: "standard",
version: "0.0.1",
},
[
"airflow",
"sensors",

View File

@@ -227,26 +227,13 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
module: "airflow.sdk",
name: (*rest).to_string(),
},
[
"airflow",
"decorators",
"base",
rest @ ("DecoratedMappedOperator"
| "DecoratedOperator"
| "TaskDecorator"
| "get_unique_task_id"
| "task_decorator_factory"),
] => Replacement::SourceModuleMoved {
module: "airflow.sdk.bases.decorator",
name: (*rest).to_string(),
},
// airflow.io
["airflow", "io", "path", "ObjectStoragePath"] => Replacement::SourceModuleMoved {
module: "airflow.sdk",
name: "ObjectStoragePath".to_string(),
},
["airflow", "io", "store", "attach"] => Replacement::SourceModuleMoved {
["airflow", "io", "storage", "attach"] => Replacement::SourceModuleMoved {
module: "airflow.sdk.io",
name: "attach".to_string(),
},
@@ -258,10 +245,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
name: (*rest).to_string(),
}
}
["airflow", "models", "Param"] => Replacement::AutoImport {
module: "airflow.sdk.definitions.param",
name: "Param",
},
// airflow.models.baseoperator
[
@@ -277,30 +260,16 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
module: "airflow.sdk",
name: "BaseOperatorLink",
},
// airflow.model..DAG
["airflow", "models", .., "DAG"] => Replacement::SourceModuleMoved {
module: "airflow.sdk",
name: "DAG".to_string(),
},
// airflow.sensors.base
[
"airflow",
"sensors",
"base",
rest @ ("BaseSensorOperator" | "PokeReturnValue" | "poke_mode_only"),
] => Replacement::SourceModuleMoved {
module: "airflow.sdk",
name: (*rest).to_string(),
},
// airflow.timetables
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => Replacement::AutoImport {
module: "airflow.timetables.assets",
name: "AssetOrTimeSchedule",
},
// airflow.utils
[
"airflow",

View File

@@ -312,7 +312,7 @@ help: Use `teardown` from `airflow.sdk` instead.
34 34 | setup()
35 |-from airflow.decorators import teardown
36 35 | from airflow.io.path import ObjectStoragePath
37 36 | from airflow.io.store import attach
37 36 | from airflow.io.storage import attach
38 37 | from airflow.models import DAG as DAGFromModel
--------------------------------------------------------------------------------
43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream
@@ -338,7 +338,7 @@ help: Use `ObjectStoragePath` from `airflow.sdk` instead.
34 34 | setup()
35 35 | from airflow.decorators import teardown
36 |-from airflow.io.path import ObjectStoragePath
37 36 | from airflow.io.store import attach
37 36 | from airflow.io.storage import attach
38 37 | from airflow.models import DAG as DAGFromModel
39 38 | from airflow.models import (
--------------------------------------------------------------------------------
@@ -350,7 +350,7 @@ help: Use `ObjectStoragePath` from `airflow.sdk` instead.
47 47 | # airflow.decorators
48 48 | teardown()
AIR311 [*] `airflow.io.store.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
AIR311 [*] `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:52:1
|
50 | # # airflow.io
@@ -366,7 +366,7 @@ help: Use `attach` from `airflow.sdk.io` instead.
34 34 | setup()
35 35 | from airflow.decorators import teardown
36 36 | from airflow.io.path import ObjectStoragePath
37 |-from airflow.io.store import attach
37 |-from airflow.io.storage import attach
38 37 | from airflow.models import DAG as DAGFromModel
39 38 | from airflow.models import (
40 39 | Connection,
@@ -391,7 +391,7 @@ AIR311 [*] `airflow.models.Connection` is removed in Airflow 3.0; It still works
help: Use `Connection` from `airflow.sdk` instead.
Unsafe fix
37 37 | from airflow.io.store import attach
37 37 | from airflow.io.storage import attach
38 38 | from airflow.models import DAG as DAGFromModel
39 39 | from airflow.models import (
40 |- Connection,
@@ -614,8 +614,6 @@ AIR311 [*] `airflow.utils.dag_parsing_context.get_parsing_context` is removed in
75 | # airflow.utils.dag_parsing_context
76 | get_parsing_context()
| ^^^^^^^^^^^^^^^^^^^
77 |
78 | from airflow.decorators.base import (
|
help: Use `get_parsing_context` from `airflow.sdk` instead.
@@ -628,211 +626,3 @@ help: Use `get_parsing_context` from `airflow.sdk` instead.
71 71 |
72 72 | # airflow.timetables.datasets
73 73 | DatasetOrTimeSchedule()
AIR311 [*] `airflow.decorators.base.DecoratedMappedOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:87:1
|
86 | # airflow.decorators.base
87 | DecoratedMappedOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^
88 | DecoratedOperator()
89 | TaskDecorator()
|
help: Use `DecoratedMappedOperator` from `airflow.sdk.bases.decorator` instead.
Unsafe fix
76 76 | get_parsing_context()
77 77 |
78 78 | from airflow.decorators.base import (
79 |- DecoratedMappedOperator,
80 79 | DecoratedOperator,
81 80 | TaskDecorator,
82 81 | get_unique_task_id,
83 82 | task_decorator_factory,
84 83 | )
84 |+from airflow.sdk.bases.decorator import DecoratedMappedOperator
85 85 |
86 86 | # airflow.decorators.base
87 87 | DecoratedMappedOperator()
AIR311 [*] `airflow.decorators.base.DecoratedOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:88:1
|
86 | # airflow.decorators.base
87 | DecoratedMappedOperator()
88 | DecoratedOperator()
| ^^^^^^^^^^^^^^^^^
89 | TaskDecorator()
90 | get_unique_task_id()
|
help: Use `DecoratedOperator` from `airflow.sdk.bases.decorator` instead.
Unsafe fix
77 77 |
78 78 | from airflow.decorators.base import (
79 79 | DecoratedMappedOperator,
80 |- DecoratedOperator,
81 80 | TaskDecorator,
82 81 | get_unique_task_id,
83 82 | task_decorator_factory,
84 83 | )
84 |+from airflow.sdk.bases.decorator import DecoratedOperator
85 85 |
86 86 | # airflow.decorators.base
87 87 | DecoratedMappedOperator()
AIR311 [*] `airflow.decorators.base.TaskDecorator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:89:1
|
87 | DecoratedMappedOperator()
88 | DecoratedOperator()
89 | TaskDecorator()
| ^^^^^^^^^^^^^
90 | get_unique_task_id()
91 | task_decorator_factory()
|
help: Use `TaskDecorator` from `airflow.sdk.bases.decorator` instead.
Unsafe fix
78 78 | from airflow.decorators.base import (
79 79 | DecoratedMappedOperator,
80 80 | DecoratedOperator,
81 |- TaskDecorator,
82 81 | get_unique_task_id,
83 82 | task_decorator_factory,
84 83 | )
84 |+from airflow.sdk.bases.decorator import TaskDecorator
85 85 |
86 86 | # airflow.decorators.base
87 87 | DecoratedMappedOperator()
AIR311 [*] `airflow.decorators.base.get_unique_task_id` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:90:1
|
88 | DecoratedOperator()
89 | TaskDecorator()
90 | get_unique_task_id()
| ^^^^^^^^^^^^^^^^^^
91 | task_decorator_factory()
|
help: Use `get_unique_task_id` from `airflow.sdk.bases.decorator` instead.
Unsafe fix
79 79 | DecoratedMappedOperator,
80 80 | DecoratedOperator,
81 81 | TaskDecorator,
82 |- get_unique_task_id,
83 82 | task_decorator_factory,
84 83 | )
84 |+from airflow.sdk.bases.decorator import get_unique_task_id
85 85 |
86 86 | # airflow.decorators.base
87 87 | DecoratedMappedOperator()
AIR311 [*] `airflow.decorators.base.task_decorator_factory` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:91:1
|
89 | TaskDecorator()
90 | get_unique_task_id()
91 | task_decorator_factory()
| ^^^^^^^^^^^^^^^^^^^^^^
|
help: Use `task_decorator_factory` from `airflow.sdk.bases.decorator` instead.
Unsafe fix
80 80 | DecoratedOperator,
81 81 | TaskDecorator,
82 82 | get_unique_task_id,
83 |- task_decorator_factory,
84 83 | )
84 |+from airflow.sdk.bases.decorator import task_decorator_factory
85 85 |
86 86 | # airflow.decorators.base
87 87 | DecoratedMappedOperator()
AIR311 [*] `airflow.models.Param` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:97:1
|
96 | # airflow.models
97 | Param()
| ^^^^^
|
help: Use `Param` from `airflow.sdk.definitions.param` instead.
Unsafe fix
91 91 | task_decorator_factory()
92 92 |
93 93 |
94 |-from airflow.models import Param
94 |+from airflow.sdk.definitions.param import Param
95 95 |
96 96 | # airflow.models
97 97 | Param()
AIR311 [*] `airflow.sensors.base.BaseSensorOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:107:1
|
106 | # airflow.sensors.base
107 | BaseSensorOperator()
| ^^^^^^^^^^^^^^^^^^
108 | PokeReturnValue()
109 | poke_mode_only()
|
help: Use `BaseSensorOperator` from `airflow.sdk` instead.
Unsafe fix
98 98 |
99 99 |
100 100 | from airflow.sensors.base import (
101 |- BaseSensorOperator,
102 101 | PokeReturnValue,
103 102 | poke_mode_only,
104 103 | )
104 |+from airflow.sdk import BaseSensorOperator
105 105 |
106 106 | # airflow.sensors.base
107 107 | BaseSensorOperator()
AIR311 [*] `airflow.sensors.base.PokeReturnValue` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:108:1
|
106 | # airflow.sensors.base
107 | BaseSensorOperator()
108 | PokeReturnValue()
| ^^^^^^^^^^^^^^^
109 | poke_mode_only()
|
help: Use `PokeReturnValue` from `airflow.sdk` instead.
Unsafe fix
99 99 |
100 100 | from airflow.sensors.base import (
101 101 | BaseSensorOperator,
102 |- PokeReturnValue,
103 102 | poke_mode_only,
104 103 | )
104 |+from airflow.sdk import PokeReturnValue
105 105 |
106 106 | # airflow.sensors.base
107 107 | BaseSensorOperator()
AIR311 [*] `airflow.sensors.base.poke_mode_only` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
--> AIR311_names.py:109:1
|
107 | BaseSensorOperator()
108 | PokeReturnValue()
109 | poke_mode_only()
| ^^^^^^^^^^^^^^
|
help: Use `poke_mode_only` from `airflow.sdk` instead.
Unsafe fix
100 100 | from airflow.sensors.base import (
101 101 | BaseSensorOperator,
102 102 | PokeReturnValue,
103 |- poke_mode_only,
104 103 | )
104 |+from airflow.sdk import poke_mode_only
105 105 |
106 106 | # airflow.sensors.base
107 107 | BaseSensorOperator()

View File

@@ -137,7 +137,7 @@ impl AutoPythonType {
let expr = Expr::Name(ast::ExprName {
id: Name::from(binding),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
});
Some((expr, vec![no_return_edit]))
@@ -204,7 +204,7 @@ fn type_expr(python_type: PythonType) -> Option<Expr> {
Expr::Name(ast::ExprName {
id: name.into(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
})
}

View File

@@ -23,7 +23,6 @@ mod tests {
#[test_case(Rule::AsyncZeroSleep, Path::new("ASYNC115.py"))]
#[test_case(Rule::LongSleepNotForever, Path::new("ASYNC116.py"))]
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))]
#[test_case(Rule::BlockingHttpCallHttpxInAsyncFunction, Path::new("ASYNC212.py"))]
#[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))]
#[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
#[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))]

View File

@@ -1,145 +0,0 @@
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_semantic::analyze::typing::{TypeChecker, check_type, traverse_union_and_optional};
use ruff_text_size::Ranged;
use crate::Violation;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks that async functions do not use blocking httpx clients.
///
/// ## Why is this bad?
/// Blocking an async function via a blocking HTTP call will block the entire
/// event loop, preventing it from executing other tasks while waiting for the
/// HTTP response, negating the benefits of asynchronous programming.
///
/// Instead of using the blocking `httpx` client, use the asynchronous client.
///
/// ## Example
/// ```python
/// import httpx
///
///
/// async def fetch():
/// client = httpx.Client()
/// response = client.get(...)
/// ```
///
/// Use instead:
/// ```python
/// import httpx
///
///
/// async def fetch():
/// async with httpx.AsyncClient() as client:
/// response = await client.get(...)
/// ```
#[derive(ViolationMetadata)]
pub(crate) struct BlockingHttpCallHttpxInAsyncFunction {
name: String,
call: String,
}
impl Violation for BlockingHttpCallHttpxInAsyncFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"Blocking httpx method {name}.{call}() in async context, use httpx.AsyncClient",
name = self.name,
call = self.call,
)
}
}
struct HttpxClientChecker;
impl TypeChecker for HttpxClientChecker {
fn match_annotation(
annotation: &ruff_python_ast::Expr,
semantic: &ruff_python_semantic::SemanticModel,
) -> bool {
// match base annotation directly
if semantic
.resolve_qualified_name(annotation)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["httpx", "Client"]))
{
return true;
}
// otherwise traverse any union or optional annotation
let mut found = false;
traverse_union_and_optional(
&mut |inner_expr, _| {
if semantic
.resolve_qualified_name(inner_expr)
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["httpx", "Client"])
})
{
found = true;
}
},
semantic,
annotation,
);
found
}
fn match_initializer(
initializer: &ruff_python_ast::Expr,
semantic: &ruff_python_semantic::SemanticModel,
) -> bool {
let Expr::Call(ExprCall { func, .. }) = initializer else {
return false;
};
semantic
.resolve_qualified_name(func)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["httpx", "Client"]))
}
}
/// ASYNC212
pub(crate) fn blocking_http_call_httpx(checker: &Checker, call: &ExprCall) {
let semantic = checker.semantic();
if !semantic.in_async_context() {
return;
}
let Some(ast::ExprAttribute { value, attr, .. }) = call.func.as_attribute_expr() else {
return;
};
let Some(name) = value.as_name_expr() else {
return;
};
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
return;
};
if check_type::<HttpxClientChecker>(binding, semantic) {
if matches!(
attr.id.as_str(),
"close"
| "delete"
| "get"
| "head"
| "options"
| "patch"
| "post"
| "put"
| "request"
| "send"
| "stream"
) {
checker.report_diagnostic(
BlockingHttpCallHttpxInAsyncFunction {
name: name.id.to_string(),
call: attr.id.to_string(),
},
call.func.range(),
);
}
}
}

View File

@@ -2,7 +2,6 @@ pub(crate) use async_busy_wait::*;
pub(crate) use async_function_with_timeout::*;
pub(crate) use async_zero_sleep::*;
pub(crate) use blocking_http_call::*;
pub(crate) use blocking_http_call_httpx::*;
pub(crate) use blocking_open_call::*;
pub(crate) use blocking_process_invocation::*;
pub(crate) use blocking_sleep::*;
@@ -14,7 +13,6 @@ mod async_busy_wait;
mod async_function_with_timeout;
mod async_zero_sleep;
mod blocking_http_call;
mod blocking_http_call_httpx;
mod blocking_open_call;
mod blocking_process_invocation;
mod blocking_sleep;

View File

@@ -1,168 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
---
ASYNC212 Blocking httpx method client.close() in async context, use httpx.AsyncClient
--> ASYNC212.py:27:5
|
25 | async def foo():
26 | client = httpx.Client()
27 | client.close() # ASYNC212
| ^^^^^^^^^^^^
28 | client.delete() # ASYNC212
29 | client.get() # ASYNC212
|
ASYNC212 Blocking httpx method client.delete() in async context, use httpx.AsyncClient
--> ASYNC212.py:28:5
|
26 | client = httpx.Client()
27 | client.close() # ASYNC212
28 | client.delete() # ASYNC212
| ^^^^^^^^^^^^^
29 | client.get() # ASYNC212
30 | client.head() # ASYNC212
|
ASYNC212 Blocking httpx method client.get() in async context, use httpx.AsyncClient
--> ASYNC212.py:29:5
|
27 | client.close() # ASYNC212
28 | client.delete() # ASYNC212
29 | client.get() # ASYNC212
| ^^^^^^^^^^
30 | client.head() # ASYNC212
31 | client.options() # ASYNC212
|
ASYNC212 Blocking httpx method client.head() in async context, use httpx.AsyncClient
--> ASYNC212.py:30:5
|
28 | client.delete() # ASYNC212
29 | client.get() # ASYNC212
30 | client.head() # ASYNC212
| ^^^^^^^^^^^
31 | client.options() # ASYNC212
32 | client.patch() # ASYNC212
|
ASYNC212 Blocking httpx method client.options() in async context, use httpx.AsyncClient
--> ASYNC212.py:31:5
|
29 | client.get() # ASYNC212
30 | client.head() # ASYNC212
31 | client.options() # ASYNC212
| ^^^^^^^^^^^^^^
32 | client.patch() # ASYNC212
33 | client.post() # ASYNC212
|
ASYNC212 Blocking httpx method client.patch() in async context, use httpx.AsyncClient
--> ASYNC212.py:32:5
|
30 | client.head() # ASYNC212
31 | client.options() # ASYNC212
32 | client.patch() # ASYNC212
| ^^^^^^^^^^^^
33 | client.post() # ASYNC212
34 | client.put() # ASYNC212
|
ASYNC212 Blocking httpx method client.post() in async context, use httpx.AsyncClient
--> ASYNC212.py:33:5
|
31 | client.options() # ASYNC212
32 | client.patch() # ASYNC212
33 | client.post() # ASYNC212
| ^^^^^^^^^^^
34 | client.put() # ASYNC212
35 | client.request() # ASYNC212
|
ASYNC212 Blocking httpx method client.put() in async context, use httpx.AsyncClient
--> ASYNC212.py:34:5
|
32 | client.patch() # ASYNC212
33 | client.post() # ASYNC212
34 | client.put() # ASYNC212
| ^^^^^^^^^^
35 | client.request() # ASYNC212
36 | client.send() # ASYNC212
|
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
--> ASYNC212.py:35:5
|
33 | client.post() # ASYNC212
34 | client.put() # ASYNC212
35 | client.request() # ASYNC212
| ^^^^^^^^^^^^^^
36 | client.send() # ASYNC212
37 | client.stream() # ASYNC212
|
ASYNC212 Blocking httpx method client.send() in async context, use httpx.AsyncClient
--> ASYNC212.py:36:5
|
34 | client.put() # ASYNC212
35 | client.request() # ASYNC212
36 | client.send() # ASYNC212
| ^^^^^^^^^^^
37 | client.stream() # ASYNC212
|
ASYNC212 Blocking httpx method client.stream() in async context, use httpx.AsyncClient
--> ASYNC212.py:37:5
|
35 | client.request() # ASYNC212
36 | client.send() # ASYNC212
37 | client.stream() # ASYNC212
| ^^^^^^^^^^^^^
38 |
39 | client.anything() # Ok
|
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
--> ASYNC212.py:45:5
|
44 | async def foo(client: httpx.Client):
45 | client.request() # ASYNC212
| ^^^^^^^^^^^^^^
46 | client.anything() # Ok
|
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
--> ASYNC212.py:50:5
|
49 | async def foo(client: httpx.Client | None):
50 | client.request() # ASYNC212
| ^^^^^^^^^^^^^^
51 | client.anything() # Ok
|
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
--> ASYNC212.py:55:5
|
54 | async def foo(client: Optional[httpx.Client]):
55 | client.request() # ASYNC212
| ^^^^^^^^^^^^^^
56 | client.anything() # Ok
|
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
--> ASYNC212.py:61:5
|
59 | async def foo():
60 | client: httpx.Client = ...
61 | client.request() # ASYNC212
| ^^^^^^^^^^^^^^
62 | client.anything() # Ok
|
ASYNC212 Blocking httpx method global_client.request() in async context, use httpx.AsyncClient
--> ASYNC212.py:69:5
|
68 | async def foo():
69 | global_client.request() # ASYNC212
| ^^^^^^^^^^^^^^^^^^^^^
70 | global_client.anything() # Ok
|

View File

@@ -52,13 +52,13 @@ impl AlwaysFixableViolation for AssertFalse {
fn assertion_error(msg: Option<&Expr>) -> Stmt {
Stmt::Raise(ast::StmtRaise {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
exc: Some(Box::new(Expr::Call(ast::ExprCall {
func: Box::new(Expr::Name(ast::ExprName {
id: "AssertionError".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
arguments: Arguments {
args: if let Some(msg) = msg {
@@ -68,10 +68,10 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
},
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))),
cause: None,
})

View File

@@ -113,7 +113,7 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
elts: elts.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
}
.into()

View File

@@ -53,11 +53,11 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> Str
attr: Identifier::new(name.to_string(), TextRange::default()),
ctx: ExprContext::Store,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})],
value: Box::new(value.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
generator.stmt(&stmt)
}

View File

@@ -10,7 +10,7 @@ mod tests {
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
use crate::{assert_diagnostics, settings};
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
@@ -31,24 +31,19 @@ mod tests {
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
fn preview_rules(path: &Path) -> Result<()> {
let snapshot = format!("preview_diff__{}", path.to_string_lossy());
let rules = vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
];
let settings_before = settings::LinterSettings::for_rules(rules.clone());
let settings_after = settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(rules)
};
assert_diagnostics_diff!(
snapshot,
let snapshot = format!("preview__{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_commas").join(path).as_path(),
&settings_before,
&settings_after
);
&settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
])
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -0,0 +1,33 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
invalid-syntax: Starred expression cannot be used here
--> COM81_syntax_error.py:3:5
|
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
2 | (
3 | *args
| ^^^^^
4 | )
|
invalid-syntax: Type parameter list cannot be empty
--> COM81_syntax_error.py:6:9
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
COM819 Trailing comma prohibited
--> COM81_syntax_error.py:6:38
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
help: Remove trailing comma

View File

@@ -1,136 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 6
--- Added ---
COM812 [*] Trailing comma missing
--> COM81.py:655:6
|
654 | type X[
655 | T
| ^
656 | ] = T
657 | def f[
|
help: Add trailing comma
Safe fix
652 652 | }"""
653 653 |
654 654 | type X[
655 |- T
655 |+ T,
656 656 | ] = T
657 657 | def f[
658 658 | T
COM812 [*] Trailing comma missing
--> COM81.py:658:6
|
656 | ] = T
657 | def f[
658 | T
| ^
659 | ](): pass
660 | class C[
|
help: Add trailing comma
Safe fix
655 655 | T
656 656 | ] = T
657 657 | def f[
658 |- T
658 |+ T,
659 659 | ](): pass
660 660 | class C[
661 661 | T
COM812 [*] Trailing comma missing
--> COM81.py:661:6
|
659 | ](): pass
660 | class C[
661 | T
| ^
662 | ]: pass
|
help: Add trailing comma
Safe fix
658 658 | T
659 659 | ](): pass
660 660 | class C[
661 |- T
661 |+ T,
662 662 | ]: pass
663 663 |
664 664 | type X[T,] = T
COM819 [*] Trailing comma prohibited
--> COM81.py:664:9
|
662 | ]: pass
663 |
664 | type X[T,] = T
| ^
665 | def f[T,](): pass
666 | class C[T,]: pass
|
help: Remove trailing comma
Safe fix
661 661 | T
662 662 | ]: pass
663 663 |
664 |-type X[T,] = T
664 |+type X[T] = T
665 665 | def f[T,](): pass
666 666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:665:8
|
664 | type X[T,] = T
665 | def f[T,](): pass
| ^
666 | class C[T,]: pass
|
help: Remove trailing comma
Safe fix
662 662 | ]: pass
663 663 |
664 664 | type X[T,] = T
665 |-def f[T,](): pass
665 |+def f[T](): pass
666 666 | class C[T,]: pass
COM819 [*] Trailing comma prohibited
--> COM81.py:666:10
|
664 | type X[T,] = T
665 | def f[T,](): pass
666 | class C[T,]: pass
| ^
|
help: Remove trailing comma
Safe fix
663 663 |
664 664 | type X[T,] = T
665 665 | def f[T,](): pass
666 |-class C[T,]: pass
666 |+class C[T]: pass

View File

@@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
--- Linter settings ---
-linter.preview = disabled
+linter.preview = enabled
--- Summary ---
Removed: 0
Added: 0

View File

@@ -209,18 +209,18 @@ fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -
},
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
Expr::Call(ExprCall {
func: Box::new(Expr::Name(ExprName {
id: "dict.fromkeys".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
arguments: args,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}

View File

@@ -22,7 +22,6 @@ mod tests {
#[test_case(Path::new("G002.py"))]
#[test_case(Path::new("G003.py"))]
#[test_case(Path::new("G004.py"))]
#[test_case(Path::new("G004_arg_order.py"))]
#[test_case(Path::new("G010.py"))]
#[test_case(Path::new("G101_1.py"))]
#[test_case(Path::new("G101_2.py"))]
@@ -49,24 +48,4 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_logging_format").join(path).as_path(),
&settings::LinterSettings {
logger_objects: vec!["logging_setup.logger".to_string()],
preview: settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -1,11 +1,9 @@
use ruff_python_ast::InterpolatedStringElement;
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator, StringFlags};
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator};
use ruff_python_semantic::analyze::logging;
use ruff_python_stdlib::logging::LoggingLevel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_f_string_logging_enabled;
use crate::registry::Rule;
use crate::rules::flake8_logging_format::violations::{
LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat,
@@ -13,87 +11,6 @@ use crate::rules::flake8_logging_format::violations::{
};
use crate::{Edit, Fix};
fn logging_f_string(
checker: &Checker,
msg: &Expr,
f_string: &ast::ExprFString,
arguments: &Arguments,
msg_pos: usize,
) {
// Report the diagnostic up-front so we can attach a fix later only when preview is enabled.
let mut diagnostic = checker.report_diagnostic(LoggingFString, msg.range());
// Preview gate for the automatic fix.
if !is_fix_f_string_logging_enabled(checker.settings()) {
return;
}
// If there are existing positional arguments after the message, bail out.
// This could indicate a mistake or complex usage we shouldn't try to fix.
if arguments.args.len() > msg_pos + 1 {
return;
}
let mut format_string = String::new();
let mut args: Vec<&str> = Vec::new();
// Try to reuse the first part's quote style when building the replacement.
// Default to double quotes if we can't determine it.
let quote_str = f_string
.value
.f_strings()
.next()
.map(|f| f.flags.quote_str())
.unwrap_or("\"");
for f in f_string.value.f_strings() {
for element in &f.elements {
match element {
InterpolatedStringElement::Literal(lit) => {
// If the literal text contains a '%' placeholder, bail out: mixing
// f-string interpolation with '%' placeholders is ambiguous for our
// automatic conversion, so don't offer a fix for this case.
if lit.value.as_ref().contains('%') {
return;
}
format_string.push_str(lit.value.as_ref());
}
InterpolatedStringElement::Interpolation(interpolated) => {
if interpolated.format_spec.is_some()
|| !matches!(
interpolated.conversion,
ruff_python_ast::ConversionFlag::None
)
{
return;
}
match interpolated.expression.as_ref() {
Expr::Name(name) => {
format_string.push_str("%s");
args.push(name.id.as_str());
}
_ => return,
}
}
}
}
}
if args.is_empty() {
return;
}
let replacement = format!(
"{q}{format_string}{q}, {args}",
q = quote_str,
format_string = format_string,
args = args.join(", ")
);
let fix = Fix::safe_edit(Edit::range_replacement(replacement, msg.range()));
diagnostic.set_fix(fix);
}
/// Returns `true` if the attribute is a reserved attribute on the `logging` module's `LogRecord`
/// class.
fn is_reserved_attr(attr: &str) -> bool {
@@ -125,7 +42,7 @@ fn is_reserved_attr(attr: &str) -> bool {
}
/// Check logging messages for violations.
fn check_msg(checker: &Checker, msg: &Expr, arguments: &Arguments, msg_pos: usize) {
fn check_msg(checker: &Checker, msg: &Expr) {
match msg {
// Check for string concatenation and percent format.
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
@@ -138,10 +55,8 @@ fn check_msg(checker: &Checker, msg: &Expr, arguments: &Arguments, msg_pos: usiz
_ => {}
},
// Check for f-strings.
Expr::FString(f_string) => {
if checker.is_rule_enabled(Rule::LoggingFString) {
logging_f_string(checker, msg, f_string, arguments, msg_pos);
}
Expr::FString(_) => {
checker.report_diagnostic_if_enabled(LoggingFString, msg.range());
}
// Check for .format() calls.
Expr::Call(ast::ExprCall { func, .. }) => {
@@ -253,7 +168,7 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
// G001, G002, G003, G004
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
if let Some(format_arg) = call.arguments.find_argument_value("msg", msg_pos) {
check_msg(checker, format_arg, &call.arguments, msg_pos);
check_msg(checker, format_arg);
}
// G010

View File

@@ -9,7 +9,6 @@ G004 Logging statement uses f-string
| ^^^^^^^^^^^^^^^
5 | logging.log(logging.INFO, f"Hello {name}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:5:27
@@ -21,7 +20,6 @@ G004 Logging statement uses f-string
6 |
7 | _LOGGER = logging.getLogger()
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:8:14
@@ -32,7 +30,6 @@ G004 Logging statement uses f-string
9 |
10 | logging.getLogger().info(f"{name}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:10:26
@@ -44,7 +41,6 @@ G004 Logging statement uses f-string
11 |
12 | from logging import info
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:14:6
@@ -55,7 +51,6 @@ G004 Logging statement uses f-string
| ^^^^^^^^^
15 | info(f"{__name__}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:15:6
@@ -66,156 +61,3 @@ G004 Logging statement uses f-string
16 |
17 | # Don't trigger for t-strings
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:24:14
|
22 | total = 9
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:30:13
|
28 | x = 99
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
| ^^^^^^^^^^^^^^
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:31:13
|
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
31 | logger.info(f"{x:>10} {x:{fmt}}")
| ^^^^^^^^^^^^^^^^^^^^
32 |
33 | logging.info(f"")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:33:14
|
31 | logger.info(f"{x:>10} {x:{fmt}}")
32 |
33 | logging.info(f"")
| ^^^
34 | logging.info(f"This message doesn't have any variables.")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:34:14
|
33 | logging.info(f"")
34 | logging.info(f"This message doesn't have any variables.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 | obj = {"key": "value"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:37:14
|
36 | obj = {"key": "value"}
37 | logging.info(f"Object: {obj!r}")
| ^^^^^^^^^^^^^^^^^^
38 |
39 | items_count = 3
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:40:17
|
39 | items_count = 3
40 | logging.warning(f"Items: {items_count:d}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^
41 |
42 | data = {"status": "active"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:43:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:44:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:48:14
|
47 | result = 123
48 | logging.info(f"Calculated result: {result + 100}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49 |
50 | temperature = 123
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:51:14
|
50 | temperature = 123
51 | logging.info(f"Temperature: {temperature:.1f}°C")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 |
53 | class FilePath:
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:57:14
|
55 | self.name = name
56 |
57 | logging.info(f"No changes made to {file_path.name}.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58 |
59 | user = "tron"
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:61:15
|
59 | user = "tron"
60 | balance = 123.45
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 |
63 | import logging
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:66:15
|
65 | x = 1
66 | logging.error(f"{x} -> %s", x)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@@ -1,23 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
---
G004 Logging statement uses f-string
--> G004_arg_order.py:9:14
|
7 | X = 1
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
| ^^^^^^^^^^^^
10 | logger.error(f"{Y} -> %s", X)
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_arg_order.py:10:14
|
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
10 | logger.error(f"{Y} -> %s", X)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@@ -1,291 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
---
G004 [*] Logging statement uses f-string
--> G004.py:4:14
|
3 | name = "world"
4 | logging.info(f"Hello {name}")
| ^^^^^^^^^^^^^^^
5 | logging.log(logging.INFO, f"Hello {name}")
|
help: Convert to lazy `%` formatting
Safe fix
1 1 | import logging
2 2 |
3 3 | name = "world"
4 |-logging.info(f"Hello {name}")
4 |+logging.info("Hello %s", name)
5 5 | logging.log(logging.INFO, f"Hello {name}")
6 6 |
7 7 | _LOGGER = logging.getLogger()
G004 [*] Logging statement uses f-string
--> G004.py:5:27
|
3 | name = "world"
4 | logging.info(f"Hello {name}")
5 | logging.log(logging.INFO, f"Hello {name}")
| ^^^^^^^^^^^^^^^
6 |
7 | _LOGGER = logging.getLogger()
|
help: Convert to lazy `%` formatting
Safe fix
2 2 |
3 3 | name = "world"
4 4 | logging.info(f"Hello {name}")
5 |-logging.log(logging.INFO, f"Hello {name}")
5 |+logging.log(logging.INFO, "Hello %s", name)
6 6 |
7 7 | _LOGGER = logging.getLogger()
8 8 | _LOGGER.info(f"{__name__}")
G004 [*] Logging statement uses f-string
--> G004.py:8:14
|
7 | _LOGGER = logging.getLogger()
8 | _LOGGER.info(f"{__name__}")
| ^^^^^^^^^^^^^
9 |
10 | logging.getLogger().info(f"{name}")
|
help: Convert to lazy `%` formatting
Safe fix
5 5 | logging.log(logging.INFO, f"Hello {name}")
6 6 |
7 7 | _LOGGER = logging.getLogger()
8 |-_LOGGER.info(f"{__name__}")
8 |+_LOGGER.info("%s", __name__)
9 9 |
10 10 | logging.getLogger().info(f"{name}")
11 11 |
G004 [*] Logging statement uses f-string
--> G004.py:10:26
|
8 | _LOGGER.info(f"{__name__}")
9 |
10 | logging.getLogger().info(f"{name}")
| ^^^^^^^^^
11 |
12 | from logging import info
|
help: Convert to lazy `%` formatting
Safe fix
7 7 | _LOGGER = logging.getLogger()
8 8 | _LOGGER.info(f"{__name__}")
9 9 |
10 |-logging.getLogger().info(f"{name}")
10 |+logging.getLogger().info("%s", name)
11 11 |
12 12 | from logging import info
13 13 |
G004 [*] Logging statement uses f-string
--> G004.py:14:6
|
12 | from logging import info
13 |
14 | info(f"{name}")
| ^^^^^^^^^
15 | info(f"{__name__}")
|
help: Convert to lazy `%` formatting
Safe fix
11 11 |
12 12 | from logging import info
13 13 |
14 |-info(f"{name}")
14 |+info("%s", name)
15 15 | info(f"{__name__}")
16 16 |
17 17 | # Don't trigger for t-strings
G004 [*] Logging statement uses f-string
--> G004.py:15:6
|
14 | info(f"{name}")
15 | info(f"{__name__}")
| ^^^^^^^^^^^^^
16 |
17 | # Don't trigger for t-strings
|
help: Convert to lazy `%` formatting
Safe fix
12 12 | from logging import info
13 13 |
14 14 | info(f"{name}")
15 |-info(f"{__name__}")
15 |+info("%s", __name__)
16 16 |
17 17 | # Don't trigger for t-strings
18 18 | info(t"{name}")
G004 [*] Logging statement uses f-string
--> G004.py:24:14
|
22 | total = 9
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
Safe fix
21 21 | count = 5
22 22 | total = 9
23 23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
24 |-logging.info(f"{count} out of {total} files in {directory_path} checked")
24 |+logging.info("%s out of %s files in %s checked", count, total, directory_path)
25 25 |
26 26 |
27 27 |
G004 Logging statement uses f-string
--> G004.py:30:13
|
28 | x = 99
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
| ^^^^^^^^^^^^^^
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:31:13
|
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
31 | logger.info(f"{x:>10} {x:{fmt}}")
| ^^^^^^^^^^^^^^^^^^^^
32 |
33 | logging.info(f"")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:33:14
|
31 | logger.info(f"{x:>10} {x:{fmt}}")
32 |
33 | logging.info(f"")
| ^^^
34 | logging.info(f"This message doesn't have any variables.")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:34:14
|
33 | logging.info(f"")
34 | logging.info(f"This message doesn't have any variables.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 | obj = {"key": "value"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:37:14
|
36 | obj = {"key": "value"}
37 | logging.info(f"Object: {obj!r}")
| ^^^^^^^^^^^^^^^^^^
38 |
39 | items_count = 3
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:40:17
|
39 | items_count = 3
40 | logging.warning(f"Items: {items_count:d}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^
41 |
42 | data = {"status": "active"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:43:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:44:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:48:14
|
47 | result = 123
48 | logging.info(f"Calculated result: {result + 100}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49 |
50 | temperature = 123
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:51:14
|
50 | temperature = 123
51 | logging.info(f"Temperature: {temperature:.1f}°C")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 |
53 | class FilePath:
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:57:14
|
55 | self.name = name
56 |
57 | logging.info(f"No changes made to {file_path.name}.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58 |
59 | user = "tron"
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:61:15
|
59 | user = "tron"
60 | balance = 123.45
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 |
63 | import logging
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:66:15
|
65 | x = 1
66 | logging.error(f"{x} -> %s", x)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@@ -1,23 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
---
G004 Logging statement uses f-string
--> G004_arg_order.py:9:14
|
7 | X = 1
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
| ^^^^^^^^^^^^
10 | logger.error(f"{Y} -> %s", X)
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_arg_order.py:10:14
|
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
10 | logger.error(f"{Y} -> %s", X)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@@ -327,16 +327,10 @@ impl Violation for LoggingStringConcat {
pub(crate) struct LoggingFString;
impl Violation for LoggingFString {
const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"Logging statement uses f-string".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Convert to lazy `%` formatting".to_string())
}
}
/// ## What it does

View File

@@ -178,21 +178,21 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
});
let node1 = Expr::Name(ast::ExprName {
id: arg_name.into(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let node2 = Expr::Attribute(ast::ExprAttribute {
value: Box::new(node1),
attr: Identifier::new(attr_name.to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let node3 = Expr::Call(ast::ExprCall {
func: Box::new(node2),
@@ -200,10 +200,10 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
args: Box::from([node]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let call = node3;
@@ -223,7 +223,7 @@ pub(crate) fn multiple_starts_ends_with(checker: &Checker, expr: &Expr) {
})
.collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let bool_op = node;
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -92,14 +92,14 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &Checker, expr: &'a Expr) {
Expr::Tuple(ast::ExprTuple {
elts: unique_nodes.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: false,
})
}),
value: subscript.value.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
});
let fix = Fix::applicable_edit(

View File

@@ -187,7 +187,7 @@ fn generate_pep604_fix(
op: Operator::BitOr,
right: Box::new(right.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))
} else {
Some(right.clone())
@@ -202,7 +202,7 @@ fn generate_pep604_fix(
}
static VIRTUAL_NONE_LITERAL: Expr = Expr::NoneLiteral(ExprNoneLiteral {
node_index: AtomicNodeIndex::NONE,
node_index: AtomicNodeIndex::dummy(),
range: TextRange::new(TextSize::new(0), TextSize::new(0)),
});

View File

@@ -133,17 +133,17 @@ fn generate_union_fix(
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
let new_expr = Expr::Subscript(ExprSubscript {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
value: Box::new(Expr::Name(ExprName {
id: Name::new(binding),
ctx: ExprContext::Store,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
slice: Box::new(Expr::Tuple(ExprTuple {
elts: nodes.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: false,
})),

View File

@@ -205,13 +205,13 @@ fn create_fix(
let new_literal_expr = Expr::Subscript(ast::ExprSubscript {
value: Box::new(literal_subscript.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
slice: Box::new(if literal_elements.len() > 1 {
Expr::Tuple(ast::ExprTuple {
elts: literal_elements.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: true,
})
@@ -235,7 +235,7 @@ fn create_fix(
UnionKind::BitOr => {
let none_expr = Expr::NoneLiteral(ExprNoneLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let union_expr = pep_604_union(&[new_literal_expr, none_expr]);
let content = checker.generator().expr(&union_expr);

View File

@@ -261,7 +261,7 @@ fn generate_pep604_fix(
op: Operator::BitOr,
right: Box::new(right.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))
} else {
Some(right.clone())

View File

@@ -140,12 +140,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) {
slice: Box::new(Expr::Tuple(ast::ExprTuple {
elts: literal_exprs.into_iter().cloned().collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: true,
})),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
});
@@ -164,12 +164,12 @@ pub(crate) fn unnecessary_literal_union<'a>(checker: &Checker, expr: &'a Expr) {
slice: Box::new(Expr::Tuple(ast::ExprTuple {
elts,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
parenthesized: true,
})),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
ctx: ExprContext::Load,
}))
} else {

View File

@@ -134,12 +134,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
id: Name::new_static("type"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
slice: Box::new(pep_604_union(&elts)),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
if other_exprs.is_empty() {
@@ -159,7 +159,7 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
id: Name::new_static("type"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
slice: Box::new(Expr::Subscript(ast::ExprSubscript {
value: subscript.value.clone(),
@@ -171,22 +171,22 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
id: type_member,
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
if other_exprs.is_empty() {
@@ -202,12 +202,12 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &Checker, union: &'a Expr) {
elts: exprs.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
})),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
checker.generator().expr(&union)

View File

@@ -301,7 +301,7 @@ fn elts_to_csv(elts: &[Expr], generator: Generator, flags: StringLiteralFlags) -
})
.into_boxed_str(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags,
});
Some(generator.expr(&node))
@@ -367,14 +367,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
Expr::from(ast::StringLiteral {
value: Box::from(*name),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags: checker.default_string_flags(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
@@ -404,14 +404,14 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
Expr::from(ast::StringLiteral {
value: Box::from(*name),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags: checker.default_string_flags(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node),
@@ -440,7 +440,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
elts: elts.clone(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node),
@@ -485,7 +485,7 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
elts: elts.clone(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
});
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -166,7 +166,7 @@ fn assert(expr: &Expr, msg: Option<&Expr>) -> Stmt {
test: Box::new(expr.clone()),
msg: msg.map(|msg| Box::new(msg.clone())),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}
@@ -176,7 +176,7 @@ fn compare(left: &Expr, cmp_op: CmpOp, right: &Expr) -> Expr {
ops: Box::from([cmp_op]),
comparators: Box::from([right.clone()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}
@@ -296,7 +296,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(expr.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}),
msg,
)
@@ -370,7 +370,7 @@ impl UnittestAssert {
};
let node = Expr::NoneLiteral(ast::ExprNoneLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
});
let expr = compare(expr, cmp_op, &node);
Ok(assert(&expr, msg))
@@ -387,7 +387,7 @@ impl UnittestAssert {
id: Name::new_static("isinstance"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
@@ -395,10 +395,10 @@ impl UnittestAssert {
args: Box::from([(**obj).clone(), (**cls).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let isinstance = node1.into();
if matches!(self, UnittestAssert::IsInstance) {
@@ -408,7 +408,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(isinstance),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let expr = node.into();
Ok(assert(&expr, msg))
@@ -429,14 +429,14 @@ impl UnittestAssert {
id: Name::new_static("re"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::ExprAttribute {
value: Box::new(node.into()),
attr: Identifier::new("search".to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
@@ -444,10 +444,10 @@ impl UnittestAssert {
args: Box::from([(**regex).clone(), (**text).clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let re_search = node2.into();
if matches!(self, UnittestAssert::Regex | UnittestAssert::RegexpMatches) {
@@ -457,7 +457,7 @@ impl UnittestAssert {
op: UnaryOp::Not,
operand: Box::new(re_search),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
Ok(assert(&node.into(), msg))
}

View File

@@ -421,7 +421,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
};
let isinstance_call = ast::ExprCall {
@@ -430,7 +430,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
id: Name::new_static("isinstance"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -438,10 +438,10 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
args: Box::from([target.clone(), tuple.into()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into();
@@ -458,7 +458,7 @@ pub(crate) fn duplicate_isinstance_call(checker: &Checker, expr: &Expr) {
.chain(after)
.collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into();
let fixed_source = checker.generator().expr(&bool_op);
@@ -552,21 +552,21 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) {
elts: comparators.into_iter().cloned().collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: true,
};
let node1 = ast::ExprName {
id: id.clone(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node2 = ast::ExprCompare {
left: Box::new(node1.into()),
ops: Box::from([CmpOp::In]),
comparators: Box::from([node.into()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let in_expr = node2.into();
let mut diagnostic = checker.report_diagnostic(
@@ -589,7 +589,7 @@ pub(crate) fn compare_with_tuple(checker: &Checker, expr: &Expr) {
op: BoolOp::Or,
values: iter::once(in_expr).chain(unmatched).collect(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
};

View File

@@ -232,7 +232,7 @@ fn check_os_environ_subscript(checker: &Checker, expr: &Expr) {
}
}),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let new_env_var = node.into();
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -188,7 +188,7 @@ pub(crate) fn if_expr_with_true_false(
id: Name::new_static("bool"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -196,10 +196,10 @@ pub(crate) fn if_expr_with_true_false(
args: Box::from([test.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -227,7 +227,7 @@ pub(crate) fn if_expr_with_false_true(
op: UnaryOp::Not,
operand: Box::new(test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}
.into(),
),
@@ -282,7 +282,7 @@ pub(crate) fn twisted_arms_in_ifexpr(
body: Box::new(node1),
orelse: Box::new(node),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node3.into()),

View File

@@ -186,7 +186,7 @@ pub(crate) fn negation_with_equal_op(checker: &Checker, expr: &Expr, op: UnaryOp
ops: Box::from([CmpOp::NotEq]),
comparators: comparators.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node.into()),
@@ -242,7 +242,7 @@ pub(crate) fn negation_with_not_equal_op(
ops: Box::from([CmpOp::Eq]),
comparators: comparators.clone(),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&node.into()),
@@ -284,7 +284,7 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera
id: Name::new_static("bool"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::ExprCall {
func: Box::new(node.into()),
@@ -292,10 +292,10 @@ pub(crate) fn double_negation(checker: &Checker, expr: &Expr, op: UnaryOp, opera
args: Box::from([*operand.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
checker.generator().expr(&node1.into()),

View File

@@ -190,7 +190,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast
attr: Identifier::new("get".to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node3 = ast::ExprCall {
func: Box::new(node2.into()),
@@ -198,17 +198,17 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &Checker, stmt_if: &ast
args: Box::from([node1, node]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node4 = expected_var.clone();
let node5 = ast::StmtAssign {
targets: vec![node4],
value: Box::new(node3.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let contents = checker.generator().stmt(&node5.into());
@@ -299,7 +299,7 @@ pub(crate) fn if_exp_instead_of_dict_get(
attr: Identifier::new("get".to_string(), TextRange::default()),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let fixed_node = ast::ExprCall {
func: Box::new(dict_get_node.into()),
@@ -307,10 +307,10 @@ pub(crate) fn if_exp_instead_of_dict_get(
args: Box::from([dict_key_node, default_value_node]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let contents = checker.generator().expr(&fixed_node.into());

View File

@@ -266,13 +266,13 @@ fn assignment_ternary(
body: Box::new(body_value.clone()),
orelse: Box::new(orelse_value.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::StmtAssign {
targets: vec![target_var.clone()],
value: Box::new(node.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node1.into()
}
@@ -282,13 +282,13 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp
op: BoolOp::And,
values: vec![left_value.clone(), right_value.clone()],
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node1 = ast::StmtAssign {
targets: vec![target_var.clone()],
value: Box::new(node.into()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node1.into()
}
@@ -296,12 +296,12 @@ fn assignment_binary_and(target_var: &Expr, left_value: &Expr, right_value: &Exp
fn assignment_binary_or(target_var: &Expr, left_value: &Expr, right_value: &Expr) -> Stmt {
(ast::StmtAssign {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
targets: vec![target_var.clone()],
value: Box::new(
(ast::ExprBoolOp {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
op: BoolOp::Or,
values: vec![left_value.clone(), right_value.clone()],
})

View File

@@ -256,7 +256,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
left: left.clone(),
comparators: Box::new([right.clone()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}))
}
@@ -264,7 +264,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
op: ast::UnaryOp::Not,
operand: Box::new(if_test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})),
}
} else if if_test.is_compare_expr() {
@@ -277,7 +277,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
id: Name::new_static("bool"),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let call_node = ast::ExprCall {
func: Box::new(func_node.into()),
@@ -285,10 +285,10 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
args: Box::from([if_test.clone()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
Some(Expr::Call(call_node))
} else {
@@ -301,7 +301,7 @@ pub(crate) fn needless_bool(checker: &Checker, stmt: &Stmt) {
Stmt::Return(ast::StmtReturn {
value: Some(Box::new(expr.clone())),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
});

View File

@@ -165,7 +165,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
ops: Box::from([op]),
comparators: Box::from([comparator.clone()]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
} else {
@@ -173,7 +173,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
op: UnaryOp::Not,
operand: Box::new(loop_.test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
}
@@ -182,7 +182,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &Checker, stmt: &Stmt) {
op: UnaryOp::Not,
operand: Box::new(loop_.test.clone()),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
node.into()
}
@@ -406,17 +406,17 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
ifs: vec![],
is_async: false,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
}],
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
parenthesized: false,
};
let node1 = ast::ExprName {
id,
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node2 = ast::ExprCall {
func: Box::new(node1.into()),
@@ -424,15 +424,15 @@ fn return_stmt(id: Name, test: &Expr, target: &Expr, iter: &Expr, generator: Gen
args: Box::from([node.into()]),
keywords: Box::from([]),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
},
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let node3 = ast::StmtReturn {
value: Some(Box::new(node2.into())),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
generator.stmt(&node3.into())
}

View File

@@ -163,14 +163,14 @@ fn construct_replacement(elts: &[&str], flags: StringLiteralFlags) -> Expr {
Expr::from(StringLiteral {
value: Box::from(*elt),
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
flags: element_flags,
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
})
}

View File

@@ -101,7 +101,7 @@ fn fix_banned_relative_import(
names: names.clone(),
level: 0,
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
};
let content = generator.stmt(&node.into());
Some(Fix::unsafe_edit(Edit::range_replacement(

View File

@@ -436,7 +436,7 @@ impl<'a> QuoteAnnotator<'a> {
let annotation = subgenerator.expr(&expr_without_forward_references);
generator.expr(&Expr::from(ast::StringLiteral {
range: TextRange::default(),
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
value: annotation.into_boxed_str(),
flags: self.flags,
}))

View File

@@ -1,10 +1,9 @@
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_semantic::{SemanticModel, analyze::typing};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::{Applicability, Edit, Fix, Violation};
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_semantic::{SemanticModel, analyze::typing};
use ruff_text_size::Ranged;
pub(crate) fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool {
arguments
@@ -184,17 +183,3 @@ pub(crate) fn check_os_pathlib_two_arg_calls(
});
}
}
pub(crate) fn has_unknown_keywords_or_starred_expr(
arguments: &ast::Arguments,
allowed: &[&str],
) -> bool {
if arguments.args.iter().any(Expr::is_starred_expr) {
return true;
}
arguments.keywords.iter().any(|kw| match &kw.arg {
Some(arg) => !allowed.contains(&arg.as_str()),
None => true,
})
}

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