From 090f6580d39a4b10082492bb277bc4c9234d242e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:24:13 +0100 Subject: [PATCH 01/54] Update rust crate regex to v1.10.4 (#10689) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad3740b61c111..28594c73253ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1885,9 +1885,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", From b9dfa7845f71303ed054e4cda437a38d929b392d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:25:05 +0100 Subject: [PATCH 02/54] chore(deps): update rust crate memchr to v2.7.2 (#10688) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28594c73253ee..2cbf55f666f6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1376,9 +1376,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mimalloc" From 2ea0c3dce6412990d522e262828a247c746d9e07 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 1 Apr 2024 12:13:14 +0100 Subject: [PATCH 03/54] Give renovate more time in which to file PRs (#10692) --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index fc5adae88f6ca..2cc847d53532c 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -4,7 +4,7 @@ suppressNotifications: ["prEditedNotification"], extends: ["config:recommended"], labels: ["internal"], - schedule: ["before 4am on Monday"], + schedule: ["on Monday"], separateMajorMinor: false, enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "npm"], cargo: { From 786ff403b15cc976b58e50ea23f90d9870f61fde Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:24:15 +0000 Subject: [PATCH 04/54] chore(deps): update rust crate serde_json to v1.0.115 (#10693) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cbf55f666f6e..81db7b900823c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2658,9 +2658,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", From d7a6978e0559c950d48a76e1e989da800e631474 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:24:48 +0000 Subject: [PATCH 05/54] chore(deps): update rust crate syn to v2.0.57 (#10694) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81db7b900823c..a1f7a7b0c54db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,7 +365,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -596,7 +596,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -607,7 +607,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1099,7 +1099,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1282,7 +1282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dbd2f3cd9346422ebdc3a614aed6969d4e0b3e9c10517f33b30326acf894c11" dependencies = [ "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1727,7 +1727,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -1945,7 +1945,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2195,7 +2195,7 @@ dependencies = [ "proc-macro2", "quote", "ruff_python_trivia", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2642,7 +2642,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2675,7 +2675,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2716,7 +2716,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2826,7 +2826,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2848,9 +2848,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -2930,7 +2930,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -2941,7 +2941,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", "test-case-core", ] @@ -2962,7 +2962,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3083,7 +3083,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3324,7 +3324,7 @@ checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3409,7 +3409,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", "wasm-bindgen-shared", ] @@ -3443,7 +3443,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3476,7 +3476,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -3741,7 +3741,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] From 8d547ef83a85a5cabe749a4cf09a41f931dc8116 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 1 Apr 2024 12:43:07 +0100 Subject: [PATCH 06/54] Allow renovate to create more PRs at once (#10695) --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 2cc847d53532c..43664e30d9bd6 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -6,6 +6,7 @@ labels: ["internal"], schedule: ["on Monday"], separateMajorMinor: false, + prHourlyLimit: 10, enabledManagers: ["github-actions", "pre-commit", "cargo", "pep621", "npm"], cargo: { // See https://docs.renovatebot.com/configuration-options/#rangestrategy From 76d0edbbaa1f601dea3feb05e74ab2a41e8e4ab7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:53:43 +0000 Subject: [PATCH 07/54] chore(deps): update rust crate toml to v0.8.12 (#10696) --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1f7a7b0c54db..052946f63461d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3031,9 +3031,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -3052,9 +3052,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.7" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap", "serde", From a32e70d449335253eaf03070ffce0f097cfd5d84 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:55:05 +0000 Subject: [PATCH 08/54] chore(deps): update rust crate insta-cmd to 0.5.0 (#10702) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 052946f63461d..1e33ad375db5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1063,9 +1063,9 @@ dependencies = [ [[package]] name = "insta-cmd" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809d3023d1d6e8d5c2206f199251f75cb26180e41f18cb0f22dd119161cb5127" +checksum = "1980f17994b79f75670aa90cfc8d35edc4aa248f16aa48b5e27835b080e452a2" dependencies = [ "insta", "serde", diff --git a/Cargo.toml b/Cargo.toml index b6f731973f2a2..f5211ed3a52c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ imperative = { version = "1.0.4" } indicatif = { version = "0.17.8" } indoc = { version = "2.0.4" } insta = { version = "1.35.1", feature = ["filters", "glob"] } -insta-cmd = { version = "0.4.0" } +insta-cmd = { version = "0.5.0" } is-macro = { version = "0.3.5" } is-wsl = { version = "0.4.0" } itertools = { version = "0.12.1" } From 20a2e25cb0489f6a17b71bef3fd0c02b23df3c39 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 12:37:41 +0000 Subject: [PATCH 09/54] chore(deps): update rust crate serde_with to v3.7.0 (#10706) --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e33ad375db5a..275ebb8cf3fa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2698,9 +2698,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" dependencies = [ "serde", "serde_derive", @@ -2709,9 +2709,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" dependencies = [ "darling", "proc-macro2", From 85d59198aae1956cd86db79a82088474e4c6a2ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:19:28 +0100 Subject: [PATCH 10/54] chore(deps): update tj-actions/changed-files action to v44 (#10712) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b8b3415a8027c..90baa30d0f4e8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,7 +35,7 @@ jobs: with: fetch-depth: 0 - - uses: tj-actions/changed-files@v43 + - uses: tj-actions/changed-files@v44 id: changed with: files_yaml: | From 46369d48fe1089d48e0590fcf6fb028218ab84b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:20:05 +0100 Subject: [PATCH 11/54] chore(deps): update rust crate uuid to v1.8.0 (#10710) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 275ebb8cf3fa2..b465fc88e268d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3306,9 +3306,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "rand", @@ -3318,9 +3318,9 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" +checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2" dependencies = [ "proc-macro2", "quote", From d021cac0c9755e7f34ee3941c9a98aa7befa9750 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:21:50 +0100 Subject: [PATCH 12/54] chore(deps): update rust crate tracing-tree to 0.3.0 (#10709) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 32 +++++++++++++++----------------- Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b465fc88e268d..906768628f149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1484,6 +1484,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -3108,17 +3117,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -3137,7 +3135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.46.0", "once_cell", "regex", "sharded-slab", @@ -3145,18 +3143,18 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] name = "tracing-tree" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ec6adcab41b1391b08a308cc6302b79f8095d1673f6947c2dc65ffb028b0b2d" +checksum = "65139ecd2c3f6484c3b99bc01c77afe21e95473630747c7aca525e78b0666675" dependencies = [ - "nu-ansi-term", + "nu-ansi-term 0.49.0", "tracing-core", - "tracing-log 0.1.4", + "tracing-log", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index f5211ed3a52c8..ea09970cc5f77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ toml = { version = "0.8.11" } tracing = { version = "0.1.40" } tracing-indicatif = { version = "0.3.6" } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -tracing-tree = { version = "0.2.4" } +tracing-tree = { version = "0.3.0" } typed-arena = { version = "2.0.2" } unic-ucd-category = { version = "0.9" } unicode-ident = { version = "1.0.12" } From 20d69ea504b95e4cd7751dbb5a17aa446721945d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:45:07 -0400 Subject: [PATCH 13/54] chore(deps): update npm development dependencies (#10697) --- playground/api/package-lock.json | 63 ++++++++++---------------------- playground/api/package.json | 2 +- playground/package-lock.json | 41 +++++++++------------ 3 files changed, 38 insertions(+), 68 deletions(-) diff --git a/playground/api/package-lock.json b/playground/api/package-lock.json index ac1395ff80011..dff1478cf2686 100644 --- a/playground/api/package-lock.json +++ b/playground/api/package-lock.json @@ -16,7 +16,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.37.0" + "wrangler": "3.41.0" } }, "node_modules/@cloudflare/kv-asset-handler": { @@ -109,9 +109,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20240320.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240320.1.tgz", - "integrity": "sha512-CiYtVpQURPgQqtBKkmOAnfPElVZuD7Xyf1IxKtKp2B4aB9gnooapwJhzeY8c4Ls4u17SgMS0MprOkrgYwzZ6xg==", + "version": "4.20240329.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240329.0.tgz", + "integrity": "sha512-AbzgvSQjG8Nci4xxQEcjTTVjiWXgOQnFIbIHtEZXteHiMGDXMWGegjWBo5JHGsZCq+U5V/SD5EnlypQnUQEoig==", "dev": true }, "node_modules/@cspotcode/source-map-support": { @@ -559,20 +559,6 @@ "node": ">=16.13" } }, - "node_modules/@miniflare/shared": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.14.0.tgz", - "integrity": "sha512-O0jAEdMkp8BzrdFCfMWZu76h4Cq+tt3/oDtcTFgzum3fRW5vUhIi/5f6bfndu6rkGbSlzxwor8CJWpzityXGug==", - "dependencies": { - "@types/better-sqlite3": "^7.6.0", - "kleur": "^4.1.4", - "npx-import": "^1.1.4", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=16.13" - } - }, "node_modules/@miniflare/storage-memory": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.14.2.tgz", @@ -905,15 +891,6 @@ "source-map": "^0.6.1" } }, - "node_modules/get-source/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -1090,9 +1067,9 @@ } }, "node_modules/miniflare": { - "version": "3.20240320.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240320.0.tgz", - "integrity": "sha512-4M2QRxs+J5sUsybBzKT++tlbrjjjGZdtWxKmj2sqLsT26dGaKDz7DxjAeF5XIhKa5cADcffygjxx4EvfWocMmw==", + "version": "3.20240320.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240320.1.tgz", + "integrity": "sha512-MoHhT+XaFPQtplNIkJc5NtWOi5u/7VkmBUWyyxDH7ehHk4xRT2PDkMCvVOUIcaqbHNIBzigyoYegdYmZcYtdCg==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -1376,6 +1353,15 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -1527,9 +1513,9 @@ } }, "node_modules/wrangler": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.37.0.tgz", - "integrity": "sha512-dffPF92EApW77lIYXxz0DMoMm2LPMlFNlgrQ0jNj7g7Mm/AaogtSuY7jXNLSeoniYNHL/57V7wlVBn82aYIqyg==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.41.0.tgz", + "integrity": "sha512-iPFHF+88ZchnoZaQnq69Qkcpt0/LwkD44FzTxHWGzuBiNFwWwaNZ5zJ1G7Ga4nyipwcgtj+ykGlB/Amdgmut7w==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.1", @@ -1538,7 +1524,7 @@ "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.17.19", - "miniflare": "3.20240320.0", + "miniflare": "3.20240320.1", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "resolve": "^1.22.8", @@ -1566,15 +1552,6 @@ } } }, - "node_modules/wrangler/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", diff --git a/playground/api/package.json b/playground/api/package.json index af705921be240..167c00ec3e88e 100644 --- a/playground/api/package.json +++ b/playground/api/package.json @@ -5,7 +5,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.37.0" + "wrangler": "3.41.0" }, "private": true, "scripts": { diff --git a/playground/package-lock.json b/playground/package-lock.json index 238366c40a962..9a79c877c722f 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -1046,31 +1046,24 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.71", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.71.tgz", - "integrity": "sha512-PxEsB9OjmQeYGffoWnYAd/r5FiJuUw2niFQHPc2v2idwh8wGPkkYzOHuinNJJY6NZqfoTCiOIizDOz38gYNsyw==", + "version": "18.2.73", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.73.tgz", + "integrity": "sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==", "dev": true, "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.22", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", - "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", + "version": "18.2.23", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.23.tgz", + "integrity": "sha512-ZQ71wgGOTmDYpnav2knkjr3qXdAFu0vsk8Ci5w3pGAIdj7/kKAyn+VsQDhXsmzzzepAiI9leWMmubXz690AI/A==", "dev": true, "dependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -3492,9 +3485,9 @@ } }, "node_modules/jiti": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", - "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -4792,9 +4785,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -4805,7 +4798,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -5074,13 +5067,13 @@ "dev": true }, "node_modules/vite": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", - "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", + "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", - "postcss": "^8.4.36", + "postcss": "^8.4.38", "rollup": "^4.13.0" }, "bin": { From 4047d456b6e1b5bfd67cf81b921350e2f2989a6c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:44:30 +0000 Subject: [PATCH 14/54] chore(deps): update rust crate insta to v1.38.0 (#10701) --- Cargo.lock | 14 ++------------ crates/ruff/src/version.rs | 8 ++++---- crates/ruff_python_resolver/src/lib.rs | 4 +--- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 906768628f149..eb8ca8b72c872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1046,9 +1046,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.35.1" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2" +checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" dependencies = [ "console", "globset", @@ -1058,7 +1058,6 @@ dependencies = [ "serde", "similar", "walkdir", - "yaml-rust", ] [[package]] @@ -3698,15 +3697,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yansi" version = "0.5.1" diff --git a/crates/ruff/src/version.rs b/crates/ruff/src/version.rs index f79b938f653c4..09ed1a5c95d15 100644 --- a/crates/ruff/src/version.rs +++ b/crates/ruff/src/version.rs @@ -70,7 +70,7 @@ pub(crate) fn version() -> VersionInfo { #[cfg(test)] mod tests { - use insta::{assert_display_snapshot, assert_json_snapshot}; + use insta::{assert_json_snapshot, assert_snapshot}; use super::{CommitInfo, VersionInfo}; @@ -80,7 +80,7 @@ mod tests { version: "0.0.0".to_string(), commit_info: None, }; - assert_display_snapshot!(version); + assert_snapshot!(version); } #[test] @@ -95,7 +95,7 @@ mod tests { commits_since_last_tag: 0, }), }; - assert_display_snapshot!(version); + assert_snapshot!(version); } #[test] @@ -110,7 +110,7 @@ mod tests { commits_since_last_tag: 24, }), }; - assert_display_snapshot!(version); + assert_snapshot!(version); } #[test] diff --git a/crates/ruff_python_resolver/src/lib.rs b/crates/ruff_python_resolver/src/lib.rs index 52be653d6cdc9..91993cadefe35 100644 --- a/crates/ruff_python_resolver/src/lib.rs +++ b/crates/ruff_python_resolver/src/lib.rs @@ -132,9 +132,7 @@ mod tests { ($value: ident) => {{ // The debug representation for the backslash are two backslashes (escaping) let $value = std::format!("{:#?}", $value).replace("\\\\", "/"); - // `insta::assert_snapshot` uses the debug representation of the string, which would - // be a single line containing `\n` - insta::assert_display_snapshot!($value); + insta::assert_snapshot!($value); }}; } From 7042b9b16dcb91ffa6a49bab6e8d43066131af2a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:54:58 -0400 Subject: [PATCH 15/54] fix(deps): update rust crate similar to v2.5.0 (#10711) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb8ca8b72c872..a937f40ce1560 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2753,9 +2753,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "similar" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "siphasher" From 2740fab7ad8963892d15581e624764c7a7894999 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 1 Apr 2024 16:57:07 +0100 Subject: [PATCH 16/54] Renovate: group all `strum` dependencies together (#10714) --- .github/renovate.json5 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 43664e30d9bd6..42e836ad3a8c5 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -46,6 +46,12 @@ matchPackagePatterns: ["monaco"], description: "Weekly update of the Monaco editor", }, + { + groupName: "strum", + matchManagers: ["cargo"], + matchPackagePatterns: ["strum"], + description: "Weekly update of strum dependencies", + }, ], vulnerabilityAlerts: { commitMessageSuffix: "", From a0e15448488badd81b0924653c180cd18029b2c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:02:18 +0000 Subject: [PATCH 17/54] chore(deps): update rust crate pep440_rs to 0.5.0 (#10703) --- Cargo.lock | 26 ++++++++++++++++++++---- Cargo.toml | 2 +- crates/ruff_linter/src/settings/types.rs | 2 +- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a937f40ce1560..79c6ceab3e9bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1642,6 +1642,18 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "pep440_rs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efd4d885c29126cc93e12af3087896e2518bd5ca0fb328c19c4ef9cecfa8be" +dependencies = [ + "once_cell", + "serde", + "unicode-width", + "unscanny", +] + [[package]] name = "pep508_rs" version = "0.3.0" @@ -1649,7 +1661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "910c513bea0f4f833122321c0f20e8c704e01de98692f6989c2ec21f43d88b1e" dependencies = [ "once_cell", - "pep440_rs", + "pep440_rs 0.4.0", "regex", "serde", "thiserror", @@ -1782,7 +1794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95c3dd745f99aa3c554b7bb00859f7d18c2f1d6afd749ccc86d60b61e702abd9" dependencies = [ "indexmap", - "pep440_rs", + "pep440_rs 0.4.0", "pep508_rs", "serde", "toml", @@ -2159,7 +2171,7 @@ dependencies = [ "once_cell", "path-absolutize", "pathdiff", - "pep440_rs", + "pep440_rs 0.5.0", "pyproject-toml", "quick-junit", "regex", @@ -2481,7 +2493,7 @@ dependencies = [ "itertools 0.12.1", "log", "path-absolutize", - "pep440_rs", + "pep440_rs 0.5.0", "regex", "ruff_cache", "ruff_formatter", @@ -3260,6 +3272,12 @@ dependencies = [ "rand", ] +[[package]] +name = "unscanny" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index ea09970cc5f77..7972eec6f06b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ num_cpus = { version = "1.16.0" } once_cell = { version = "1.19.0" } path-absolutize = { version = "3.1.1" } pathdiff = { version = "0.2.1" } -pep440_rs = { version = "0.4.0", features = ["serde"] } +pep440_rs = { version = "0.5.0", features = ["serde"] } pretty_assertions = "1.3.0" proc-macro2 = { version = "1.0.79" } pyproject-toml = { version = "0.9.0" } diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index d89c3844c208d..4e4c8fbf978c5 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -539,7 +539,7 @@ impl SerializationFormat { pub struct RequiredVersion(VersionSpecifiers); impl TryFrom for RequiredVersion { - type Error = pep440_rs::Pep440Error; + type Error = pep440_rs::VersionSpecifiersParseError; fn try_from(value: String) -> Result { // Treat `0.3.1` as `==0.3.1`, for backwards compatibility. From 221b3236a8770e2f9a0c502290b78a5ec58698cc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:06:51 +0000 Subject: [PATCH 18/54] chore(deps): update strum to 0.26.0 (#10715) --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79c6ceab3e9bd..ee5caeaf0f9e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2829,18 +2829,18 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "strum" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ "heck 0.4.1", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 7972eec6f06b7..8349034ddbb9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,8 +90,8 @@ shlex = { version = "1.3.0" } similar = { version = "2.4.0", features = ["inline"] } smallvec = { version = "1.13.2" } static_assertions = "1.1.0" -strum = { version = "0.25.0", features = ["strum_macros"] } -strum_macros = { version = "0.25.3" } +strum = { version = "0.26.0", features = ["strum_macros"] } +strum_macros = { version = "0.26.0" } syn = { version = "2.0.55" } tempfile = { version = "3.9.0" } test-case = { version = "3.3.1" } From 23e8279093d8f891fc8bb59ebea2532649801ed3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:10:33 +0000 Subject: [PATCH 19/54] chore(deps): update npm development dependencies (#10716) --- playground/api/package-lock.json | 64 ++++++++++++++++---------------- playground/api/package.json | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/playground/api/package-lock.json b/playground/api/package-lock.json index dff1478cf2686..293caa2cdfd25 100644 --- a/playground/api/package-lock.json +++ b/playground/api/package-lock.json @@ -16,7 +16,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.41.0" + "wrangler": "3.42.0" } }, "node_modules/@cloudflare/kv-asset-handler": { @@ -29,9 +29,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20240320.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240320.1.tgz", - "integrity": "sha512-ioG5k2M17xyiAlK/k3L21NZLMVeSHMjwlmGtZyCyzSLL5/zGINcgZ5yPLV0UuWiysw07/6Jjzm5Sx94hzMVybg==", + "version": "1.20240329.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240329.0.tgz", + "integrity": "sha512-/raHmsHrYjoC5am84wqyiZIDCRrrYN6YDFb4zchwWQzJ0ZHleUeY6IzNdjujrS/gYey/+Db9oyl2PD1xAZt4gA==", "cpu": [ "x64" ], @@ -45,9 +45,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20240320.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240320.1.tgz", - "integrity": "sha512-Ga6RDdnFEIsN4WuWsaP9bLGvK9K7pEIVoSIgmw6vweVlD8UK/a2MPGrsF1ogwdeCTCOMY8wUh9poL/Yu48IPpg==", + "version": "1.20240329.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240329.0.tgz", + "integrity": "sha512-3wnwVdfFDt+JUhlA6NWW+093ryGNF0HMuBmkOh0PG6j4GMRH8Y+EDsqzqrzT3ZoGGXbI9x1H7k15VKb3LAN/KA==", "cpu": [ "arm64" ], @@ -61,9 +61,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20240320.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240320.1.tgz", - "integrity": "sha512-KFof5H8eU0NXv+pUAU7Lk/OLtOmfsioTJqu0v6kPL7QsTGsgzj5sEQNcQ8DONSze549Yflu5W00qpA2cPz9eWQ==", + "version": "1.20240329.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240329.0.tgz", + "integrity": "sha512-E909ZIXgjdr2iuq5bF/vq02elizDlPQoYRiKojdvODC7w8rbnpwnuptajS4xK5kmKh4XBiU2o9NDhut/W1kfyw==", "cpu": [ "x64" ], @@ -77,9 +77,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20240320.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240320.1.tgz", - "integrity": "sha512-t+kGc6dGdkKvVMGcHCPhlCsUZF5dj8xbAFvLB7DAJ8T79ys30rmY2Lu/C8vKlhjH9TJhbzgKmPaJ0wC/K4euvw==", + "version": "1.20240329.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240329.0.tgz", + "integrity": "sha512-PELA3FVW75pKchsSI5o40oiClFY2Uiq+KUx/f/srwz2pIJoM5YWLmFrv+s8feKoEwuabxIGSzHxy7QA++HyprQ==", "cpu": [ "arm64" ], @@ -93,9 +93,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20240320.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240320.1.tgz", - "integrity": "sha512-9xDylCOsuzWqGuANkuUByiJ5RHeMqgw37FiI7rn8I6zdGAc/alOB9B4Bh7B73WC2uEpFL+XCEjcHZ6NmsO4NaQ==", + "version": "1.20240329.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240329.0.tgz", + "integrity": "sha512-/T+AcjVqTuqAeGBQmjAF4TOTm8sv3BSO/NtUPa1ghCvsp1sb03L6/c3wFc9ZonSdRYeBb0XDX7PnenGCvjr/Tw==", "cpu": [ "x64" ], @@ -1067,9 +1067,9 @@ } }, "node_modules/miniflare": { - "version": "3.20240320.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240320.1.tgz", - "integrity": "sha512-MoHhT+XaFPQtplNIkJc5NtWOi5u/7VkmBUWyyxDH7ehHk4xRT2PDkMCvVOUIcaqbHNIBzigyoYegdYmZcYtdCg==", + "version": "3.20240329.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240329.0.tgz", + "integrity": "sha512-kdHlMwhV241kck5oh8uyKPIhCusP1BL4+iOSeJZgcJ46EATA6crWtYqlARNU9t/iYXhzKhXOlOPJjjlCJuOgTA==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -1080,7 +1080,7 @@ "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.2", - "workerd": "1.20240320.1", + "workerd": "1.20240329.0", "ws": "^8.11.0", "youch": "^3.2.2", "zod": "^3.20.6" @@ -1493,9 +1493,9 @@ } }, "node_modules/workerd": { - "version": "1.20240320.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240320.1.tgz", - "integrity": "sha512-nuavAGGjh0qqM6RF5zxTHyUwEqdLCHchodbrpbh/xlJpFGnJVY5C1YgSi2S9aLkJJoa0/25Ta/+EzXEbApA/3w==", + "version": "1.20240329.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240329.0.tgz", + "integrity": "sha512-6wWuMOwWsp3K6447XsI/MZYFq0KlpV2zVbbNFEkv3N7UgJJKaHGwL/hilr6RlS4UFLU4co8nrF2lc5uR781HKg==", "dev": true, "hasInstallScript": true, "bin": { @@ -1505,17 +1505,17 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20240320.1", - "@cloudflare/workerd-darwin-arm64": "1.20240320.1", - "@cloudflare/workerd-linux-64": "1.20240320.1", - "@cloudflare/workerd-linux-arm64": "1.20240320.1", - "@cloudflare/workerd-windows-64": "1.20240320.1" + "@cloudflare/workerd-darwin-64": "1.20240329.0", + "@cloudflare/workerd-darwin-arm64": "1.20240329.0", + "@cloudflare/workerd-linux-64": "1.20240329.0", + "@cloudflare/workerd-linux-arm64": "1.20240329.0", + "@cloudflare/workerd-windows-64": "1.20240329.0" } }, "node_modules/wrangler": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.41.0.tgz", - "integrity": "sha512-iPFHF+88ZchnoZaQnq69Qkcpt0/LwkD44FzTxHWGzuBiNFwWwaNZ5zJ1G7Ga4nyipwcgtj+ykGlB/Amdgmut7w==", + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.42.0.tgz", + "integrity": "sha512-t/Fq80aG5RrCyN118aV9oG1Tt66QEatz0tarKzpy0cuUMUf3T3yJAuYb6kmYIy6+beQJNoGWSAjjhAQnOn2MCQ==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.1", @@ -1524,7 +1524,7 @@ "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.17.19", - "miniflare": "3.20240320.1", + "miniflare": "3.20240329.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "resolve": "^1.22.8", diff --git a/playground/api/package.json b/playground/api/package.json index 167c00ec3e88e..107cdc7d91090 100644 --- a/playground/api/package.json +++ b/playground/api/package.json @@ -5,7 +5,7 @@ "@cloudflare/workers-types": "^4.20230801.0", "miniflare": "^3.20230801.1", "typescript": "^5.1.6", - "wrangler": "3.41.0" + "wrangler": "3.42.0" }, "private": true, "scripts": { From 200ebeebdc8ab8ee14f56922e21b218f41a5a7e4 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 1 Apr 2024 13:15:32 -0400 Subject: [PATCH 20/54] Bump version to v0.3.5 (#10717) --- CHANGELOG.md | 62 +++++++++++++++++++++++++++---- Cargo.lock | 6 +-- README.md | 2 +- crates/ruff/Cargo.toml | 2 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_shrinking/Cargo.toml | 2 +- docs/integrations.md | 6 +-- pyproject.toml | 2 +- scripts/benchmarks/pyproject.toml | 2 +- 9 files changed, 67 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a150d771cf83d..e92349fca5bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## 0.3.5 + +### Preview features + +- \[`pylint`\] Implement `modified-iterating-set` (`E4703`) ([#10473](https://github.com/astral-sh/ruff/pull/10473)) +- \[`refurb`\] Implement `for-loop-set-mutations` (`FURB142`) ([#10583](https://github.com/astral-sh/ruff/pull/10583)) +- \[`refurb`\] Implement `unnecessary-from-float` (`FURB164`) ([#10647](https://github.com/astral-sh/ruff/pull/10647)) +- \[`refurb`\] Implement `verbose-decimal-constructor` (`FURB157`) ([#10533](https://github.com/astral-sh/ruff/pull/10533)) + +### Rule changes + +- \[`flake8-comprehensions`\] Handled special case for `C401` which also matches `C416` ([#10596](https://github.com/astral-sh/ruff/pull/10596)) +- \[`flake8-pyi`\] Mark `unaliased-collections-abc-set-import` fix as "safe" for more cases in stub files (`PYI025`) ([#10547](https://github.com/astral-sh/ruff/pull/10547)) +- \[`numpy`\] Add `row_stack` to NumPy 2.0 migration rule ([#10646](https://github.com/astral-sh/ruff/pull/10646)) +- \[`pycodestyle`\] Allow cell magics before an import (`E402`) ([#10545](https://github.com/astral-sh/ruff/pull/10545)) +- \[`pycodestyle`\] Avoid blank line rules for the first logical line in cell ([#10291](https://github.com/astral-sh/ruff/pull/10291)) + +### Configuration + +- Respected nested namespace packages ([#10541](https://github.com/astral-sh/ruff/pull/10541)) +- \[`flake8-boolean-trap`\] Add setting for user defined allowed boolean trap ([#10531](https://github.com/astral-sh/ruff/pull/10531)) + +### Bug fixes + +- Correctly handle references in `__all__` definitions when renaming symbols in autofixes ([#10527](https://github.com/astral-sh/ruff/pull/10527)) +- Track ranges of names inside `__all__` definitions ([#10525](https://github.com/astral-sh/ruff/pull/10525)) +- \[`flake8-bugbear`\] Avoid false positive for usage after `continue` (`B031`) ([#10539](https://github.com/astral-sh/ruff/pull/10539)) +- \[`flake8-copyright`\] Accept commas in default copyright pattern ([#9498](https://github.com/astral-sh/ruff/pull/9498)) +- \[`flake8-datetimez`\] Allow f-strings with `%z` for `DTZ007` ([#10651](https://github.com/astral-sh/ruff/pull/10651)) +- \[`flake8-pytest-style`\] Fix `PT014` autofix for last item in list ([#10532](https://github.com/astral-sh/ruff/pull/10532)) +- \[`flake8-quotes`\] Ignore `Q000`, `Q001` when string is inside forward ref ([#10585](https://github.com/astral-sh/ruff/pull/10585)) +- \[`isort`\] Always place non-relative imports after relative imports ([#10669](https://github.com/astral-sh/ruff/pull/10669)) +- \[`isort`\] Respect Unicode characters in import sorting ([#10529](https://github.com/astral-sh/ruff/pull/10529)) +- \[`pyflakes`\] Fix F821 false negatives when `from __future__ import annotations` is active (attempt 2) ([#10524](https://github.com/astral-sh/ruff/pull/10524)) +- \[`pyflakes`\] Make `unnecessary-lambda` an always-unsafe fix ([#10668](https://github.com/astral-sh/ruff/pull/10668)) +- \[`pylint`\] Fixed false-positive on the rule `PLW1641` (`eq-without-hash`) ([#10566](https://github.com/astral-sh/ruff/pull/10566)) +- \[`ruff`\] Fix panic in unused `# noqa` removal with multi-byte space (`RUF100`) ([#10682](https://github.com/astral-sh/ruff/pull/10682)) + +### Documentation + +- Add PR title format to `CONTRIBUTING.md` ([#10665](https://github.com/astral-sh/ruff/pull/10665)) +- Fix list markup to include blank lines required ([#10591](https://github.com/astral-sh/ruff/pull/10591)) +- Put `flake8-logging` next to the other flake8 plugins in registry ([#10587](https://github.com/astral-sh/ruff/pull/10587)) +- \[`flake8-bandit`\] Update warning message for rule `S305` to address insecure block cipher mode use ([#10602](https://github.com/astral-sh/ruff/pull/10602)) +- \[`flake8-bugbear`\] Document use of anonymous assignment in `useless-expression` ([#10551](https://github.com/astral-sh/ruff/pull/10551)) +- \[`flake8-datetimez`\] Clarify error messages and docs for `DTZ` rules ([#10621](https://github.com/astral-sh/ruff/pull/10621)) +- \[`pycodestyle`\] Use same before vs. after numbers for `space-around-operator` ([#10640](https://github.com/astral-sh/ruff/pull/10640)) +- \[`ruff`\] Change `quadratic-list-summation` docs to use `iadd` consistently ([#10666](https://github.com/astral-sh/ruff/pull/10666)) + ## 0.3.4 ### Preview features @@ -97,7 +146,7 @@ - Fix unstable `with` items formatting ([#10274](https://github.com/astral-sh/ruff/pull/10274)) - Avoid repeating function calls in f-string conversions ([#10265](https://github.com/astral-sh/ruff/pull/10265)) - Fix E203 false positive for slices in format strings ([#10280](https://github.com/astral-sh/ruff/pull/10280)) -- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283)) +- Fix incorrect `Parameter` range for `*args` and `**kwargs` ([#10283](https://github.com/astral-sh/ruff/pull/10283)) - Treat `typing.Annotated` subscripts as type definitions ([#10285](https://github.com/astral-sh/ruff/pull/10285)) ## 0.3.1 @@ -205,8 +254,7 @@ This release introduces the Ruff 2024.2 style, stabilizing the following changes Highlights include: - Initial support formatting f-strings (in `--preview`). -- Support for overriding arbitrary configuration options via the CLI through an expanded `--config` - argument (e.g., `--config "lint.isort.combine-as-imports=false"`). +- Support for overriding arbitrary configuration options via the CLI through an expanded `--config` argument (e.g., `--config "lint.isort.combine-as-imports=false"`). - Significant performance improvements in Ruff's lexer, parser, and lint rules. ### Preview features @@ -854,7 +902,7 @@ docstrings via the `docstring-code-format` setting. - \[`pylint`\] Default `max-positional-args` to `max-args` ([#8998](https://github.com/astral-sh/ruff/pull/8998)) - \[`pylint`\] Add `allow-dunder-method-names` setting for `bad-dunder-method-name` (`PLW3201`) ([#8812](https://github.com/astral-sh/ruff/pull/8812)) - \[`isort`\] Add support for `from-first` setting ([#8663](https://github.com/astral-sh/ruff/pull/8663)) -- \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841)) +- \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841)) ### Bug fixes @@ -983,7 +1031,7 @@ docstrings via the `docstring-code-format` setting. - \[`flake8-trio`\] Implement `TRIO115` ([#8486](https://github.com/astral-sh/ruff/pull/8486)) - \[`refurb`\] Implement `type-none-comparison` (`FURB169`) ([#8487](https://github.com/astral-sh/ruff/pull/8487)) - Flag all comparisons against builtin types in `E721` ([#8491](https://github.com/astral-sh/ruff/pull/8491)) -- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525)) +- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525)) ### Formatter @@ -1151,7 +1199,7 @@ Try it today with `ruff format`! [Check out the blog post](https://astral.sh/blo - Add `backports.strenum` to `deprecated-imports` ([#8113](https://github.com/astral-sh/ruff/pull/8113)) - Update `SIM112` to ignore `https_proxy`, `http_proxy`, and `no_proxy` ([#8140](https://github.com/astral-sh/ruff/pull/8140)) - Update fix for `literal-membership` (`PLR6201`) to be unsafe ([#8097](https://github.com/astral-sh/ruff/pull/8097)) -- Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108)) +- Update fix for `mutable-argument-defaults` (`B006`) to be unsafe ([#8108](https://github.com/astral-sh/ruff/pull/8108)) ### Formatter @@ -1279,7 +1327,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/). - \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815)) - \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811)) -*New rules are added in [preview](https://docs.astral.sh/ruff/preview/).* +_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._ ### Configuration diff --git a/Cargo.lock b/Cargo.lock index ee5caeaf0f9e9..a43b7e51ddcf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1985,7 +1985,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "argfile", @@ -2147,7 +2147,7 @@ dependencies = [ [[package]] name = "ruff_linter" -version = "0.3.4" +version = "0.3.5" dependencies = [ "aho-corasick", "annotate-snippets 0.9.2", @@ -2420,7 +2420,7 @@ dependencies = [ [[package]] name = "ruff_shrinking" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "clap", diff --git a/README.md b/README.md index 5d7c185f3394c..9096df4e74dad 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,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.3.4 + rev: v0.3.5 hooks: # Run the linter. - id: ruff diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 3a95c40f470ce..c7ab422c8b91f 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff" -version = "0.3.4" +version = "0.3.5" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index c7c49f86fdfd2..cee9e18a6b559 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_linter" -version = "0.3.4" +version = "0.3.5" publish = false authors = { workspace = true } edition = { workspace = true } diff --git a/crates/ruff_shrinking/Cargo.toml b/crates/ruff_shrinking/Cargo.toml index 6a9b51047ec7c..0527b86387c53 100644 --- a/crates/ruff_shrinking/Cargo.toml +++ b/crates/ruff_shrinking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ruff_shrinking" -version = "0.3.4" +version = "0.3.5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/docs/integrations.md b/docs/integrations.md index 2304286ed0522..fe1a45882b10d 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -14,7 +14,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.3.4 + rev: v0.3.5 hooks: # Run the linter. - id: ruff @@ -27,7 +27,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.3.4 + rev: v0.3.5 hooks: # Run the linter. - id: ruff @@ -41,7 +41,7 @@ To run the hooks over Jupyter Notebooks too, add `jupyter` to the list of allowe ```yaml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.3.4 + rev: v0.3.5 hooks: # Run the linter. - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 39471ada77f31..c1db0a858587e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ruff" -version = "0.3.4" +version = "0.3.5" description = "An extremely fast Python linter and code formatter, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] readme = "README.md" diff --git a/scripts/benchmarks/pyproject.toml b/scripts/benchmarks/pyproject.toml index 14e5c802aa425..c34cf9fa30d9e 100644 --- a/scripts/benchmarks/pyproject.toml +++ b/scripts/benchmarks/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scripts" -version = "0.3.4" +version = "0.3.5" description = "" authors = ["Charles Marsh "] From 67f0f615b2e82b9663b28ed50d1120ecd4c912e9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 1 Apr 2024 13:40:55 -0400 Subject: [PATCH 21/54] Recursively resolve `TypeDicts` for N815 violations (#10719) ## Summary Only works within a single file for now. Closes https://github.com/astral-sh/ruff/issues/10671. --- .../resources/test/fixtures/pep8_naming/N815.py | 7 +++++++ .../src/checkers/ast/analyze/expression.rs | 8 ++------ crates/ruff_linter/src/rules/pep8_naming/helpers.rs | 13 +++++-------- .../rules/mixed_case_variable_in_class_scope.rs | 7 +++---- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pep8_naming/N815.py b/crates/ruff_linter/resources/test/fixtures/pep8_naming/N815.py index d3578d3cfbd7d..41a9f46fd8f96 100644 --- a/crates/ruff_linter/resources/test/fixtures/pep8_naming/N815.py +++ b/crates/ruff_linter/resources/test/fixtures/pep8_naming/N815.py @@ -21,3 +21,10 @@ class D(TypedDict): mixedCase: bool _mixedCase: list mixed_Case: set + +class E(D): + lower: int + CONSTANT: str + mixedCase: bool + _mixedCase: list + mixed_Case: set diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 9e85625e87eba..f7fb21373d889 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -223,14 +223,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { } } if checker.enabled(Rule::MixedCaseVariableInClassScope) { - if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) = - &checker.semantic.current_scope().kind + if let ScopeKind::Class(class_def) = &checker.semantic.current_scope().kind { pep8_naming::rules::mixed_case_variable_in_class_scope( - checker, - expr, - id, - arguments.as_deref(), + checker, expr, id, class_def, ); } } diff --git a/crates/ruff_linter/src/rules/pep8_naming/helpers.rs b/crates/ruff_linter/src/rules/pep8_naming/helpers.rs index 068c4b143b95e..e94ace527c638 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/helpers.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/helpers.rs @@ -1,8 +1,8 @@ use itertools::Itertools; use ruff_python_ast::name::UnqualifiedName; -use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; -use ruff_python_semantic::SemanticModel; +use ruff_python_ast::{self as ast, Expr, Stmt}; +use ruff_python_semantic::{analyze, SemanticModel}; use ruff_python_stdlib::str::{is_cased_lowercase, is_cased_uppercase}; pub(super) fn is_camelcase(name: &str) -> bool { @@ -86,16 +86,13 @@ pub(super) fn is_type_alias_assignment(stmt: &Stmt, semantic: &SemanticModel) -> } /// Returns `true` if the statement is an assignment to a `TypedDict`. -pub(super) fn is_typed_dict_class(arguments: Option<&Arguments>, semantic: &SemanticModel) -> bool { +pub(super) fn is_typed_dict_class(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { if !semantic.seen_typing() { return false; } - arguments.is_some_and(|arguments| { - arguments - .args - .iter() - .any(|base| semantic.match_typing_expr(base, "TypedDict")) + analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| { + semantic.match_typing_qualified_name(&qualified_name, "TypedDict") }) } diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs index 6ebed08650a61..15b11ac894bd1 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/mixed_case_variable_in_class_scope.rs @@ -1,7 +1,6 @@ -use ruff_python_ast::{Arguments, Expr}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -55,7 +54,7 @@ pub(crate) fn mixed_case_variable_in_class_scope( checker: &mut Checker, expr: &Expr, name: &str, - arguments: Option<&Arguments>, + class_def: &ast::StmtClassDef, ) { if !helpers::is_mixed_case(name) { return; @@ -64,7 +63,7 @@ pub(crate) fn mixed_case_variable_in_class_scope( let parent = checker.semantic().current_statement(); if helpers::is_named_tuple_assignment(parent, checker.semantic()) - || helpers::is_typed_dict_class(arguments, checker.semantic()) + || helpers::is_typed_dict_class(class_def, checker.semantic()) { return; } From d36f60999d30a2433ad4b126f8b094e4a5565d2b Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 1 Apr 2024 15:44:45 -0400 Subject: [PATCH 22/54] Ignore annotated lambdas in class scopes (#10720) ## Summary An annotated lambda assignment within a class scope is often intentional. For example, within a dataclass or Pydantic model, these are treated as fields rather than methods (and so can be passed values in constructors). I originally wrote this to special-case dataclasses and Pydantic models... But was left feeling like we'd see more false positives here for little gain (an annotated lambda within a `class` is likely intentional?). Open to opinions, though. Closes https://github.com/astral-sh/ruff/issues/10718. --- .../test/fixtures/pycodestyle/E731.py | 11 +++++++- .../src/rules/pycodestyle/helpers.rs | 1 + .../pycodestyle/rules/lambda_assignment.rs | 25 +++++++++++-------- ...les__pycodestyle__tests__E731_E731.py.snap | 24 +++--------------- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py index 7c01e74a0343c..a8acec8eec562 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E731.py @@ -60,7 +60,7 @@ class Scope: class Scope: from typing import Callable - # E731 + # OK f: Callable[[int], int] = lambda x: 2 * x @@ -147,3 +147,12 @@ def scope(): f = lambda: ( i := 1, ) + + +from dataclasses import dataclass +from typing import Callable + +@dataclass +class FilterDataclass: + # OK + filter: Callable[[str], bool] = lambda _: True diff --git a/crates/ruff_linter/src/rules/pycodestyle/helpers.rs b/crates/ruff_linter/src/rules/pycodestyle/helpers.rs index 0c7b133787f99..2b39a6dad110b 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/helpers.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/helpers.rs @@ -1,3 +1,4 @@ +/// Returns `true` if the name should be considered "ambiguous". pub(super) fn is_ambiguous_name(name: &str) -> bool { name == "l" || name == "I" || name == "O" } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index 26242791398b0..776b2e8842d0c 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -1,14 +1,13 @@ +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{ self as ast, Expr, Identifier, Parameter, ParameterWithDefault, Parameters, Stmt, }; -use ruff_text_size::{Ranged, TextRange}; - -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; -use ruff_macros::{derive_message_formats, violation}; use ruff_python_codegen::Generator; use ruff_python_semantic::SemanticModel; use ruff_python_trivia::{has_leading_content, has_trailing_content, leading_indentation}; use ruff_source_file::UniversalNewlines; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -106,12 +105,17 @@ pub(crate) fn lambda_assignment( } } - // If the assignment is in a class body, it might not be safe to replace it because the - // assignment might be carrying a type annotation that will be used by some package like - // dataclasses, which wouldn't consider the rewritten function definition to be - // equivalent. Even if it _doesn't_ have an annotation, rewriting safely would require - // making this a static method. - // See: https://github.com/astral-sh/ruff/issues/3046 + // If the assignment is a class attribute (with an annotation), ignore it. + // + // This is most common for, e.g., dataclasses and Pydantic models. Those libraries will + // treat the lambda as an assignable field, and the use of a lambda is almost certainly + // intentional. + if annotation.is_some() && checker.semantic().current_scope().kind.is_class() { + return; + } + + // Otherwise, if the assignment is in a class body, flag it, but use a display-only fix. + // Rewriting safely would require making this a static method. // // Similarly, if the lambda is shadowing a variable in the current scope, // rewriting it as a function declaration may break type-checking. @@ -179,6 +183,7 @@ fn extract_types(annotation: &Expr, semantic: &SemanticModel) -> Option<(Vec, diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap index 9711dbafcdd9f..32d034f1e76d7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E731_E731.py.snap @@ -120,25 +120,6 @@ E731.py:57:5: E731 Do not assign a `lambda` expression, use a `def` 59 60 | 60 61 | class Scope: -E731.py:64:5: E731 Do not assign a `lambda` expression, use a `def` - | -63 | # E731 -64 | f: Callable[[int], int] = lambda x: 2 * x - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E731 - | - = help: Rewrite `f` as a `def` - -ℹ Display-only fix -61 61 | from typing import Callable -62 62 | -63 63 | # E731 -64 |- f: Callable[[int], int] = lambda x: 2 * x - 64 |+ def f(x: int) -> int: - 65 |+ return 2 * x -65 66 | -66 67 | -67 68 | def scope(): - E731.py:73:9: E731 Do not assign a `lambda` expression, use a `def` | 71 | x: Callable[[int], int] @@ -383,5 +364,6 @@ E731.py:147:5: E731 [*] Do not assign a `lambda` expression, use a `def` 149 |- ) 147 |+ def f(): 148 |+ return (i := 1), - - +150 149 | +151 150 | +152 151 | from dataclasses import dataclass From 7b48443624db9b1594a194a91483d53f5e6566d7 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 1 Apr 2024 23:21:12 -0400 Subject: [PATCH 23/54] Respect `Q00*` ignores in `flake8-quotes` rules (#10728) ## Summary We lost the per-rule ignores when these were migrated to the AST, so if _any_ `Q` rule is enabled, they're now all enabled. Closes https://github.com/astral-sh/ruff/issues/10724. ## Test Plan Ran: ```shell ruff check . --isolated --select Q --ignore Q000 ruff check . --isolated --select Q --ignore Q001 ruff check . --isolated --select Q --ignore Q002 ruff check . --isolated --select Q --ignore Q000,Q001 ruff check . --isolated --select Q --ignore Q000,Q002 ruff check . --isolated --select Q --ignore Q001,Q002 ``` ...against: ```python ''' bad docsting ''' a = 'single' b = ''' bad multi line ''' ``` --- .../fixtures/flake8_quotes/doubles_all.py | 7 +++ .../src/rules/flake8_quotes/mod.rs | 57 +++++++++++++++++++ .../rules/check_string_quotes.rs | 21 ++++++- ..._tests__only_docstring_doubles_all.py.snap | 18 ++++++ ...es__tests__only_inline_doubles_all.py.snap | 22 +++++++ ..._tests__only_multiline_doubles_all.py.snap | 24 ++++++++ 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_all.py create mode 100644 crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_docstring_doubles_all.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_inline_doubles_all.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_multiline_doubles_all.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_all.py b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_all.py new file mode 100644 index 0000000000000..80c0d8e22c11e --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_all.py @@ -0,0 +1,7 @@ +"""This is a docstring.""" + +this_is_an_inline_string = "double quote string" + +this_is_a_multiline_string = """ +double quote string +""" diff --git a/crates/ruff_linter/src/rules/flake8_quotes/mod.rs b/crates/ruff_linter/src/rules/flake8_quotes/mod.rs index 8a69e6c294a51..9025b10e67e55 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/mod.rs @@ -195,4 +195,61 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } + + #[test_case(Path::new("doubles_all.py"))] + fn only_inline(path: &Path) -> Result<()> { + let snapshot = format!("only_inline_{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("flake8_quotes").join(path).as_path(), + &LinterSettings { + flake8_quotes: super::settings::Settings { + inline_quotes: Quote::Single, + multiline_quotes: Quote::Single, + docstring_quotes: Quote::Single, + avoid_escape: true, + }, + ..LinterSettings::for_rules(vec![Rule::BadQuotesInlineString]) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + + #[test_case(Path::new("doubles_all.py"))] + fn only_multiline(path: &Path) -> Result<()> { + let snapshot = format!("only_multiline_{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("flake8_quotes").join(path).as_path(), + &LinterSettings { + flake8_quotes: super::settings::Settings { + inline_quotes: Quote::Single, + multiline_quotes: Quote::Single, + docstring_quotes: Quote::Single, + avoid_escape: true, + }, + ..LinterSettings::for_rules(vec![Rule::BadQuotesMultilineString]) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + + #[test_case(Path::new("doubles_all.py"))] + fn only_docstring(path: &Path) -> Result<()> { + let snapshot = format!("only_docstring_{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("flake8_quotes").join(path).as_path(), + &LinterSettings { + flake8_quotes: super::settings::Settings { + inline_quotes: Quote::Single, + multiline_quotes: Quote::Single, + docstring_quotes: Quote::Single, + avoid_escape: true, + }, + ..LinterSettings::for_rules(vec![Rule::BadQuotesDocstring]) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs index e5eb47a0dea8c..fc4ff375053ec 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs @@ -5,6 +5,7 @@ use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::registry::Rule; use super::super::settings::Quote; @@ -334,6 +335,11 @@ fn strings(checker: &mut Checker, sequence: &[TextRange]) { for (range, trivia) in sequence.iter().zip(trivia) { if trivia.is_multiline { + // If multiline strings aren't enforced, ignore it. + if !checker.enabled(Rule::BadQuotesMultilineString) { + continue; + } + // If our string is or contains a known good string, ignore it. if trivia .raw_text @@ -375,6 +381,11 @@ fn strings(checker: &mut Checker, sequence: &[TextRange]) { // If we're not using the preferred type, only allow use to avoid escapes. && !relax_quote { + // If inline strings aren't enforced, ignore it. + if !checker.enabled(Rule::BadQuotesInlineString) { + continue; + } + if trivia.has_empty_text() && text_ends_at_quote(locator, *range, quotes_settings.inline_quotes) { @@ -455,10 +466,14 @@ pub(crate) fn check_string_quotes(checker: &mut Checker, string_like: StringLike }; if checker.semantic().in_docstring() { - for range in ranges { - docstring(checker, range); + if checker.enabled(Rule::BadQuotesDocstring) { + for range in ranges { + docstring(checker, range); + } } } else { - strings(checker, &ranges); + if checker.any_enabled(&[Rule::BadQuotesInlineString, Rule::BadQuotesMultilineString]) { + strings(checker, &ranges); + } } } diff --git a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_docstring_doubles_all.py.snap b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_docstring_doubles_all.py.snap new file mode 100644 index 0000000000000..6a87062097567 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_docstring_doubles_all.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/flake8_quotes/mod.rs +--- +doubles_all.py:1:1: Q002 [*] Double quote docstring found but single quotes preferred + | +1 | """This is a docstring.""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Q002 +2 | +3 | this_is_an_inline_string = "double quote string" + | + = help: Replace double quotes docstring with single quotes + +ℹ Safe fix +1 |-"""This is a docstring.""" + 1 |+'''This is a docstring.''' +2 2 | +3 3 | this_is_an_inline_string = "double quote string" +4 4 | diff --git a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_inline_doubles_all.py.snap b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_inline_doubles_all.py.snap new file mode 100644 index 0000000000000..0ca50ac62e93a --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_inline_doubles_all.py.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/rules/flake8_quotes/mod.rs +--- +doubles_all.py:3:28: Q000 [*] Double quotes found but single quotes preferred + | +1 | """This is a docstring.""" +2 | +3 | this_is_an_inline_string = "double quote string" + | ^^^^^^^^^^^^^^^^^^^^^ Q000 +4 | +5 | this_is_a_multiline_string = """ + | + = help: Replace double quotes with single quotes + +ℹ Safe fix +1 1 | """This is a docstring.""" +2 2 | +3 |-this_is_an_inline_string = "double quote string" + 3 |+this_is_an_inline_string = 'double quote string' +4 4 | +5 5 | this_is_a_multiline_string = """ +6 6 | double quote string diff --git a/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_multiline_doubles_all.py.snap b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_multiline_doubles_all.py.snap new file mode 100644 index 0000000000000..ddffe1bb80a5f --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_quotes/snapshots/ruff_linter__rules__flake8_quotes__tests__only_multiline_doubles_all.py.snap @@ -0,0 +1,24 @@ +--- +source: crates/ruff_linter/src/rules/flake8_quotes/mod.rs +--- +doubles_all.py:5:30: Q001 [*] Double quote multiline found but single quotes preferred + | +3 | this_is_an_inline_string = "double quote string" +4 | +5 | this_is_a_multiline_string = """ + | ______________________________^ +6 | | double quote string +7 | | """ + | |___^ Q001 + | + = help: Replace double multiline quotes with single quotes + +ℹ Safe fix +2 2 | +3 3 | this_is_an_inline_string = "double quote string" +4 4 | +5 |-this_is_a_multiline_string = """ + 5 |+this_is_a_multiline_string = ''' +6 6 | double quote string +7 |-""" + 7 |+''' From 159bad73d5f9cbe1c5d4cd858067ffd92a9a2423 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 1 Apr 2024 23:47:20 -0400 Subject: [PATCH 24/54] Accept non-aliased (but correct) import in unconventional-import-alias (#10729) ## Summary Given: ```toml [tool.ruff.lint.flake8-import-conventions.aliases] "django.conf.settings" = "settings" ``` We should accept `from django.conf import settings`. Closes https://github.com/astral-sh/ruff/issues/10599. --- .../flake8_import_conventions/defaults.py | 1 + .../flake8_import_conventions/same_name.py | 10 +++++++ .../rules/flake8_import_conventions/mod.rs | 28 +++++++++++++++++-- .../rules/unconventional_import_alias.rs | 2 +- ...8_import_conventions__tests__defaults.snap | 10 ++----- ..._import_conventions__tests__same_name.snap | 17 +++++++++++ 6 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/same_name.py create mode 100644 crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__same_name.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/defaults.py b/crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/defaults.py index c1c528deca16e..0342d4203495f 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/defaults.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/defaults.py @@ -21,6 +21,7 @@ def unconventional_aliases(): import tkinter as tkr import networkx as nxy + def conventional_aliases(): import altair as alt import matplotlib.pyplot as plt diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/same_name.py b/crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/same_name.py new file mode 100644 index 0000000000000..31618078cd030 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_import_conventions/same_name.py @@ -0,0 +1,10 @@ +def no_alias(): + from django.conf import settings + + +def conventional_alias(): + from django.conf import settings as settings + + +def unconventional_alias(): + from django.conf import settings as s diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/mod.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/mod.rs index fc5c6034af93d..de5d8a89155a8 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/mod.rs @@ -27,7 +27,7 @@ mod tests { #[test] fn custom() -> Result<()> { - let mut aliases = super::settings::default_aliases(); + let mut aliases = default_aliases(); aliases.extend(FxHashMap::from_iter([ ("dask.array".to_string(), "da".to_string()), ("dask.dataframe".to_string(), "dd".to_string()), @@ -126,7 +126,7 @@ mod tests { #[test] fn override_defaults() -> Result<()> { - let mut aliases = super::settings::default_aliases(); + let mut aliases = default_aliases(); aliases.extend(FxHashMap::from_iter([( "numpy".to_string(), "nmp".to_string(), @@ -149,7 +149,7 @@ mod tests { #[test] fn from_imports() -> Result<()> { - let mut aliases = super::settings::default_aliases(); + let mut aliases = default_aliases(); aliases.extend(FxHashMap::from_iter([ ("xml.dom.minidom".to_string(), "md".to_string()), ( @@ -182,4 +182,26 @@ mod tests { assert_messages!(diagnostics); Ok(()) } + + #[test] + fn same_name() -> Result<()> { + let mut aliases = default_aliases(); + aliases.extend(FxHashMap::from_iter([( + "django.conf.settings".to_string(), + "settings".to_string(), + )])); + let diagnostics = test_path( + Path::new("flake8_import_conventions/same_name.py"), + &LinterSettings { + flake8_import_conventions: super::settings::Settings { + aliases, + banned_aliases: FxHashMap::default(), + banned_from: FxHashSet::default(), + }, + ..LinterSettings::for_rule(Rule::UnconventionalImportAlias) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs index 4db3974c965cf..9a16bb8ed325d 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs @@ -66,7 +66,7 @@ pub(crate) fn unconventional_import_alias( let expected_alias = conventions.get(qualified_name.as_str())?; let name = binding.name(checker.locator()); - if binding.is_alias() && name == expected_alias { + if name == expected_alias { return None; } diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__defaults.snap b/crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__defaults.snap index 09c79038f15b2..d3f4b0a501404 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__defaults.snap +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__defaults.snap @@ -256,7 +256,7 @@ defaults.py:21:23: ICN001 [*] `tkinter` should be imported as `tk` 21 |+ import tkinter as tk 22 22 | import networkx as nxy 23 23 | -24 24 | def conventional_aliases(): +24 24 | defaults.py:22:24: ICN001 [*] `networkx` should be imported as `nx` | @@ -264,8 +264,6 @@ defaults.py:22:24: ICN001 [*] `networkx` should be imported as `nx` 21 | import tkinter as tkr 22 | import networkx as nxy | ^^^ ICN001 -23 | -24 | def conventional_aliases(): | = help: Alias `networkx` to `nx` @@ -276,7 +274,5 @@ defaults.py:22:24: ICN001 [*] `networkx` should be imported as `nx` 22 |- import networkx as nxy 22 |+ import networkx as nx 23 23 | -24 24 | def conventional_aliases(): -25 25 | import altair as alt - - +24 24 | +25 25 | def conventional_aliases(): diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__same_name.snap b/crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__same_name.snap new file mode 100644 index 0000000000000..c30be9f37554c --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/snapshots/ruff_linter__rules__flake8_import_conventions__tests__same_name.snap @@ -0,0 +1,17 @@ +--- +source: crates/ruff_linter/src/rules/flake8_import_conventions/mod.rs +--- +same_name.py:10:41: ICN001 [*] `django.conf.settings` should be imported as `settings` + | + 9 | def unconventional_alias(): +10 | from django.conf import settings as s + | ^ ICN001 + | + = help: Alias `django.conf.settings` to `settings` + +ℹ Unsafe fix +7 7 | +8 8 | +9 9 | def unconventional_alias(): +10 |- from django.conf import settings as s + 10 |+ from django.conf import settings as settings From eee2d5b9155bd2a73d8881b9edf3ca0b99327a03 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 2 Apr 2024 15:53:20 +0530 Subject: [PATCH 25/54] Remove unused operator methods and impl (#10690) ## Summary This PR removes unused operator methods and impl traits. There is already the `is_macro::Is` implementation for all the operators and this seems unnecessary. --- crates/ruff_python_ast/src/nodes.rs | 709 ---------------------------- 1 file changed, 709 deletions(-) diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 5a3c20e4dacc3..1d47a1676f24f 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -2727,73 +2727,6 @@ pub enum ExprContext { Store, Del, } -impl ExprContext { - #[inline] - pub const fn load(&self) -> Option { - match self { - ExprContext::Load => Some(ExprContextLoad), - _ => None, - } - } - - #[inline] - pub const fn store(&self) -> Option { - match self { - ExprContext::Store => Some(ExprContextStore), - _ => None, - } - } - - #[inline] - pub const fn del(&self) -> Option { - match self { - ExprContext::Del => Some(ExprContextDel), - _ => None, - } - } -} - -pub struct ExprContextLoad; -impl From for ExprContext { - fn from(_: ExprContextLoad) -> Self { - ExprContext::Load - } -} - -impl std::cmp::PartialEq for ExprContextLoad { - #[inline] - fn eq(&self, other: &ExprContext) -> bool { - matches!(other, ExprContext::Load) - } -} - -pub struct ExprContextStore; -impl From for ExprContext { - fn from(_: ExprContextStore) -> Self { - ExprContext::Store - } -} - -impl std::cmp::PartialEq for ExprContextStore { - #[inline] - fn eq(&self, other: &ExprContext) -> bool { - matches!(other, ExprContext::Store) - } -} - -pub struct ExprContextDel; -impl From for ExprContext { - fn from(_: ExprContextDel) -> Self { - ExprContext::Del - } -} - -impl std::cmp::PartialEq for ExprContextDel { - #[inline] - fn eq(&self, other: &ExprContext) -> bool { - matches!(other, ExprContext::Del) - } -} /// See also [boolop](https://docs.python.org/3/library/ast.html#ast.BoolOp) #[derive(Clone, Debug, PartialEq, is_macro::Is, Copy, Hash, Eq)] @@ -2801,51 +2734,6 @@ pub enum BoolOp { And, Or, } -impl BoolOp { - #[inline] - pub const fn and(&self) -> Option { - match self { - BoolOp::And => Some(BoolOpAnd), - BoolOp::Or => None, - } - } - - #[inline] - pub const fn or(&self) -> Option { - match self { - BoolOp::Or => Some(BoolOpOr), - BoolOp::And => None, - } - } -} - -pub struct BoolOpAnd; -impl From for BoolOp { - fn from(_: BoolOpAnd) -> Self { - BoolOp::And - } -} - -impl std::cmp::PartialEq for BoolOpAnd { - #[inline] - fn eq(&self, other: &BoolOp) -> bool { - matches!(other, BoolOp::And) - } -} - -pub struct BoolOpOr; -impl From for BoolOp { - fn from(_: BoolOpOr) -> Self { - BoolOp::Or - } -} - -impl std::cmp::PartialEq for BoolOpOr { - #[inline] - fn eq(&self, other: &BoolOp) -> bool { - matches!(other, BoolOp::Or) - } -} /// See also [operator](https://docs.python.org/3/library/ast.html#ast.operator) #[derive(Clone, Debug, PartialEq, is_macro::Is, Copy, Hash, Eq)] @@ -2864,293 +2752,6 @@ pub enum Operator { BitAnd, FloorDiv, } -impl Operator { - #[inline] - pub const fn operator_add(&self) -> Option { - match self { - Operator::Add => Some(OperatorAdd), - _ => None, - } - } - - #[inline] - pub const fn operator_sub(&self) -> Option { - match self { - Operator::Sub => Some(OperatorSub), - _ => None, - } - } - - #[inline] - pub const fn operator_mult(&self) -> Option { - match self { - Operator::Mult => Some(OperatorMult), - _ => None, - } - } - - #[inline] - pub const fn operator_mat_mult(&self) -> Option { - match self { - Operator::MatMult => Some(OperatorMatMult), - _ => None, - } - } - - #[inline] - pub const fn operator_div(&self) -> Option { - match self { - Operator::Div => Some(OperatorDiv), - _ => None, - } - } - - #[inline] - pub const fn operator_mod(&self) -> Option { - match self { - Operator::Mod => Some(OperatorMod), - _ => None, - } - } - - #[inline] - pub const fn operator_pow(&self) -> Option { - match self { - Operator::Pow => Some(OperatorPow), - _ => None, - } - } - - #[inline] - pub const fn operator_l_shift(&self) -> Option { - match self { - Operator::LShift => Some(OperatorLShift), - _ => None, - } - } - - #[inline] - pub const fn operator_r_shift(&self) -> Option { - match self { - Operator::RShift => Some(OperatorRShift), - _ => None, - } - } - - #[inline] - pub const fn operator_bit_or(&self) -> Option { - match self { - Operator::BitOr => Some(OperatorBitOr), - _ => None, - } - } - - #[inline] - pub const fn operator_bit_xor(&self) -> Option { - match self { - Operator::BitXor => Some(OperatorBitXor), - _ => None, - } - } - - #[inline] - pub const fn operator_bit_and(&self) -> Option { - match self { - Operator::BitAnd => Some(OperatorBitAnd), - _ => None, - } - } - - #[inline] - pub const fn operator_floor_div(&self) -> Option { - match self { - Operator::FloorDiv => Some(OperatorFloorDiv), - _ => None, - } - } -} - -pub struct OperatorAdd; -impl From for Operator { - fn from(_: OperatorAdd) -> Self { - Operator::Add - } -} - -impl std::cmp::PartialEq for OperatorAdd { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::Add) - } -} - -pub struct OperatorSub; -impl From for Operator { - fn from(_: OperatorSub) -> Self { - Operator::Sub - } -} - -impl std::cmp::PartialEq for OperatorSub { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::Sub) - } -} - -pub struct OperatorMult; -impl From for Operator { - fn from(_: OperatorMult) -> Self { - Operator::Mult - } -} - -impl std::cmp::PartialEq for OperatorMult { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::Mult) - } -} - -pub struct OperatorMatMult; -impl From for Operator { - fn from(_: OperatorMatMult) -> Self { - Operator::MatMult - } -} - -impl std::cmp::PartialEq for OperatorMatMult { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::MatMult) - } -} - -pub struct OperatorDiv; -impl From for Operator { - fn from(_: OperatorDiv) -> Self { - Operator::Div - } -} - -impl std::cmp::PartialEq for OperatorDiv { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::Div) - } -} - -pub struct OperatorMod; -impl From for Operator { - fn from(_: OperatorMod) -> Self { - Operator::Mod - } -} - -impl std::cmp::PartialEq for OperatorMod { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::Mod) - } -} - -pub struct OperatorPow; -impl From for Operator { - fn from(_: OperatorPow) -> Self { - Operator::Pow - } -} - -impl std::cmp::PartialEq for OperatorPow { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::Pow) - } -} - -pub struct OperatorLShift; -impl From for Operator { - fn from(_: OperatorLShift) -> Self { - Operator::LShift - } -} - -impl std::cmp::PartialEq for OperatorLShift { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::LShift) - } -} - -pub struct OperatorRShift; -impl From for Operator { - fn from(_: OperatorRShift) -> Self { - Operator::RShift - } -} - -impl std::cmp::PartialEq for OperatorRShift { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::RShift) - } -} - -pub struct OperatorBitOr; -impl From for Operator { - fn from(_: OperatorBitOr) -> Self { - Operator::BitOr - } -} - -impl std::cmp::PartialEq for OperatorBitOr { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::BitOr) - } -} - -pub struct OperatorBitXor; -impl From for Operator { - fn from(_: OperatorBitXor) -> Self { - Operator::BitXor - } -} - -impl std::cmp::PartialEq for OperatorBitXor { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::BitXor) - } -} - -pub struct OperatorBitAnd; -impl From for Operator { - fn from(_: OperatorBitAnd) -> Self { - Operator::BitAnd - } -} - -impl std::cmp::PartialEq for OperatorBitAnd { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::BitAnd) - } -} - -pub struct OperatorFloorDiv; -impl From for Operator { - fn from(_: OperatorFloorDiv) -> Self { - Operator::FloorDiv - } -} - -impl std::cmp::PartialEq for OperatorFloorDiv { - #[inline] - fn eq(&self, other: &Operator) -> bool { - matches!(other, Operator::FloorDiv) - } -} /// See also [unaryop](https://docs.python.org/3/library/ast.html#ast.unaryop) #[derive(Clone, Debug, PartialEq, is_macro::Is, Copy, Hash, Eq)] @@ -3160,95 +2761,6 @@ pub enum UnaryOp { UAdd, USub, } -impl UnaryOp { - #[inline] - pub const fn invert(&self) -> Option { - match self { - UnaryOp::Invert => Some(UnaryOpInvert), - _ => None, - } - } - - #[inline] - pub const fn not(&self) -> Option { - match self { - UnaryOp::Not => Some(UnaryOpNot), - _ => None, - } - } - - #[inline] - pub const fn u_add(&self) -> Option { - match self { - UnaryOp::UAdd => Some(UnaryOpUAdd), - _ => None, - } - } - - #[inline] - pub const fn u_sub(&self) -> Option { - match self { - UnaryOp::USub => Some(UnaryOpUSub), - _ => None, - } - } -} - -pub struct UnaryOpInvert; -impl From for UnaryOp { - fn from(_: UnaryOpInvert) -> Self { - UnaryOp::Invert - } -} - -impl std::cmp::PartialEq for UnaryOpInvert { - #[inline] - fn eq(&self, other: &UnaryOp) -> bool { - matches!(other, UnaryOp::Invert) - } -} - -pub struct UnaryOpNot; -impl From for UnaryOp { - fn from(_: UnaryOpNot) -> Self { - UnaryOp::Not - } -} - -impl std::cmp::PartialEq for UnaryOpNot { - #[inline] - fn eq(&self, other: &UnaryOp) -> bool { - matches!(other, UnaryOp::Not) - } -} - -pub struct UnaryOpUAdd; -impl From for UnaryOp { - fn from(_: UnaryOpUAdd) -> Self { - UnaryOp::UAdd - } -} - -impl std::cmp::PartialEq for UnaryOpUAdd { - #[inline] - fn eq(&self, other: &UnaryOp) -> bool { - matches!(other, UnaryOp::UAdd) - } -} - -pub struct UnaryOpUSub; -impl From for UnaryOp { - fn from(_: UnaryOpUSub) -> Self { - UnaryOp::USub - } -} - -impl std::cmp::PartialEq for UnaryOpUSub { - #[inline] - fn eq(&self, other: &UnaryOp) -> bool { - matches!(other, UnaryOp::USub) - } -} /// See also [cmpop](https://docs.python.org/3/library/ast.html#ast.cmpop) #[derive(Clone, Debug, PartialEq, is_macro::Is, Copy, Hash, Eq)] @@ -3264,227 +2776,6 @@ pub enum CmpOp { In, NotIn, } -impl CmpOp { - #[inline] - pub const fn cmp_op_eq(&self) -> Option { - match self { - CmpOp::Eq => Some(CmpOpEq), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_not_eq(&self) -> Option { - match self { - CmpOp::NotEq => Some(CmpOpNotEq), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_lt(&self) -> Option { - match self { - CmpOp::Lt => Some(CmpOpLt), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_lt_e(&self) -> Option { - match self { - CmpOp::LtE => Some(CmpOpLtE), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_gt(&self) -> Option { - match self { - CmpOp::Gt => Some(CmpOpGt), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_gt_e(&self) -> Option { - match self { - CmpOp::GtE => Some(CmpOpGtE), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_is(&self) -> Option { - match self { - CmpOp::Is => Some(CmpOpIs), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_is_not(&self) -> Option { - match self { - CmpOp::IsNot => Some(CmpOpIsNot), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_in(&self) -> Option { - match self { - CmpOp::In => Some(CmpOpIn), - _ => None, - } - } - - #[inline] - pub const fn cmp_op_not_in(&self) -> Option { - match self { - CmpOp::NotIn => Some(CmpOpNotIn), - _ => None, - } - } -} - -pub struct CmpOpEq; -impl From for CmpOp { - fn from(_: CmpOpEq) -> Self { - CmpOp::Eq - } -} - -impl std::cmp::PartialEq for CmpOpEq { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::Eq) - } -} - -pub struct CmpOpNotEq; -impl From for CmpOp { - fn from(_: CmpOpNotEq) -> Self { - CmpOp::NotEq - } -} - -impl std::cmp::PartialEq for CmpOpNotEq { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::NotEq) - } -} - -pub struct CmpOpLt; -impl From for CmpOp { - fn from(_: CmpOpLt) -> Self { - CmpOp::Lt - } -} - -impl std::cmp::PartialEq for CmpOpLt { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::Lt) - } -} - -pub struct CmpOpLtE; -impl From for CmpOp { - fn from(_: CmpOpLtE) -> Self { - CmpOp::LtE - } -} - -impl std::cmp::PartialEq for CmpOpLtE { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::LtE) - } -} - -pub struct CmpOpGt; -impl From for CmpOp { - fn from(_: CmpOpGt) -> Self { - CmpOp::Gt - } -} - -impl std::cmp::PartialEq for CmpOpGt { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::Gt) - } -} - -pub struct CmpOpGtE; -impl From for CmpOp { - fn from(_: CmpOpGtE) -> Self { - CmpOp::GtE - } -} - -impl std::cmp::PartialEq for CmpOpGtE { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::GtE) - } -} - -pub struct CmpOpIs; -impl From for CmpOp { - fn from(_: CmpOpIs) -> Self { - CmpOp::Is - } -} - -impl std::cmp::PartialEq for CmpOpIs { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::Is) - } -} - -pub struct CmpOpIsNot; -impl From for CmpOp { - fn from(_: CmpOpIsNot) -> Self { - CmpOp::IsNot - } -} - -impl std::cmp::PartialEq for CmpOpIsNot { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::IsNot) - } -} - -pub struct CmpOpIn; -impl From for CmpOp { - fn from(_: CmpOpIn) -> Self { - CmpOp::In - } -} - -impl std::cmp::PartialEq for CmpOpIn { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::In) - } -} - -pub struct CmpOpNotIn; -impl From for CmpOp { - fn from(_: CmpOpNotIn) -> Self { - CmpOp::NotIn - } -} - -impl std::cmp::PartialEq for CmpOpNotIn { - #[inline] - fn eq(&self, other: &CmpOp) -> bool { - matches!(other, CmpOp::NotIn) - } -} /// See also [comprehension](https://docs.python.org/3/library/ast.html#ast.comprehension) #[derive(Clone, Debug, PartialEq)] From 99dd3a8ab05a94d3ffb2529c06ab6ed59a2dbf82 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 2 Apr 2024 16:04:36 +0530 Subject: [PATCH 26/54] Implement `as_str` & `Display` for all operator enums (#10691) ## Summary This PR adds the `as_str` implementation for all the operator methods. It already exists for `CmpOp` which is being [used in the linter](https://github.com/astral-sh/ruff/blob/ffcd77860c316bfe5709551389eb52e5feb702f6/crates/ruff_linter/src/rules/flake8_simplify/rules/key_in_dict.rs#L117) and it makes sense to implement it for the rest as well. This will also be utilized in error messages for the new parser. --- crates/ruff_python_ast/src/nodes.rs | 98 ++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 1d47a1676f24f..09de037850f69 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -2735,6 +2735,21 @@ pub enum BoolOp { Or, } +impl BoolOp { + pub const fn as_str(&self) -> &'static str { + match self { + BoolOp::And => "and", + BoolOp::Or => "or", + } + } +} + +impl fmt::Display for BoolOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + /// See also [operator](https://docs.python.org/3/library/ast.html#ast.operator) #[derive(Clone, Debug, PartialEq, is_macro::Is, Copy, Hash, Eq)] pub enum Operator { @@ -2753,6 +2768,32 @@ pub enum Operator { FloorDiv, } +impl Operator { + pub const fn as_str(&self) -> &'static str { + match self { + Operator::Add => "+", + Operator::Sub => "-", + Operator::Mult => "*", + Operator::MatMult => "@", + Operator::Div => "/", + Operator::Mod => "%", + Operator::Pow => "**", + Operator::LShift => "<<", + Operator::RShift => ">>", + Operator::BitOr => "|", + Operator::BitXor => "^", + Operator::BitAnd => "&", + Operator::FloorDiv => "//", + } + } +} + +impl fmt::Display for Operator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + /// See also [unaryop](https://docs.python.org/3/library/ast.html#ast.unaryop) #[derive(Clone, Debug, PartialEq, is_macro::Is, Copy, Hash, Eq)] pub enum UnaryOp { @@ -2762,6 +2803,23 @@ pub enum UnaryOp { USub, } +impl UnaryOp { + pub const fn as_str(&self) -> &'static str { + match self { + UnaryOp::Invert => "~", + UnaryOp::Not => "not", + UnaryOp::UAdd => "+", + UnaryOp::USub => "-", + } + } +} + +impl fmt::Display for UnaryOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + /// See also [cmpop](https://docs.python.org/3/library/ast.html#ast.cmpop) #[derive(Clone, Debug, PartialEq, is_macro::Is, Copy, Hash, Eq)] pub enum CmpOp { @@ -2777,6 +2835,29 @@ pub enum CmpOp { NotIn, } +impl CmpOp { + pub const fn as_str(&self) -> &'static str { + match self { + CmpOp::Eq => "==", + CmpOp::NotEq => "!=", + CmpOp::Lt => "<", + CmpOp::LtE => "<=", + CmpOp::Gt => ">", + CmpOp::GtE => ">=", + CmpOp::Is => "is", + CmpOp::IsNot => "is not", + CmpOp::In => "in", + CmpOp::NotIn => "not in", + } + } +} + +impl fmt::Display for CmpOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + /// See also [comprehension](https://docs.python.org/3/library/ast.html#ast.comprehension) #[derive(Clone, Debug, PartialEq)] pub struct Comprehension { @@ -3282,23 +3363,6 @@ impl Deref for TypeParams { pub type Suite = Vec; -impl CmpOp { - pub fn as_str(&self) -> &'static str { - match self { - CmpOp::Eq => "==", - CmpOp::NotEq => "!=", - CmpOp::Lt => "<", - CmpOp::LtE => "<=", - CmpOp::Gt => ">", - CmpOp::GtE => ">=", - CmpOp::Is => "is", - CmpOp::IsNot => "is not", - CmpOp::In => "in", - CmpOp::NotIn => "not in", - } - } -} - impl Parameters { pub fn empty(range: TextRange) -> Self { Self { From 0d20ec968f389f2d9dc5cbf60978fafe30dce58b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 2 Apr 2024 14:50:19 +0200 Subject: [PATCH 27/54] Build codspeed benchmarks by calling cargo directly (#10735) --- .github/workflows/ci.yaml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 90baa30d0f4e8..ef1f69dd19d34 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -525,8 +525,23 @@ jobs: - uses: Swatinem/rust-cache@v2 + # Codspeed comes with a very ancient cargo version (1.66) that resolves features flags differently than what we use now. + # This can result in build failures; see https://github.com/astral-sh/ruff/pull/10700. + # There's a pending codspeed PR to upgrade to a newer cargo version, but until that's merged, we need to use the workaround below. + # https://github.com/CodSpeedHQ/codspeed-rust/pull/31 + # What we do is to call cargo build manually with the correct feature flags and RUSTC settings. We'll have to + # manually maintain the list of benchmarks to run with codspeed (the benefit is that we could detect which benchmarks to run and build based on the changes). + # This is inspired by https://github.com/oxc-project/oxc/blob/a0532adc654039a0c7ead7b35216dfa0b0cb8e8f/.github/workflows/benchmark.yml - name: "Build benchmarks" - run: cargo codspeed build --features codspeed -p ruff_benchmark + env: + RUSTFLAGS: "-C debuginfo=2 -C strip=none -g --cfg codspeed" + shell: bash + # Build all benchmarks, copy the binary to the codspeed directory, remove any `*.d` files that might have been created. + run: | + cargo build --release -p ruff_benchmark --bench parser --bench linter --bench formatter --bench lexer --features=codspeed + mkdir -p ./target/codspeed/ruff_benchmark + cp ./target/release/deps/{lexer,parser,linter,formatter}* target/codspeed/ruff_benchmark/ + rm -rf ./target/codspeed/ruff_benchmark/*.d - name: "Run benchmarks" uses: CodSpeedHQ/action@v2 From b90e6df5cca7990a50f15eaea240ce2d3cb0f51b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:03:54 +0200 Subject: [PATCH 28/54] chore(deps): update rust crate env_logger to 0.11.0 (#10700) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 29 +++++++++++++++-------------- Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a43b7e51ddcf8..fd72c72219b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,17 +711,27 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -2900,15 +2910,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 8349034ddbb9f..f967ca8cd3a33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ criterion = { version = "0.5.1", default-features = false } crossbeam = { version = "0.8.4" } dirs = { version = "5.0.0" } drop_bomb = { version = "0.1.5" } -env_logger = { version = "0.10.1" } +env_logger = { version = "0.11.0" } fern = { version = "0.6.1" } filetime = { version = "0.2.23" } fs-err = { version = "2.11.0" } From 0de23760ff2a06cf0d3e01c05429307e20747fb0 Mon Sep 17 00:00:00 2001 From: Aleksei Latyshev Date: Tue, 2 Apr 2024 18:47:31 +0200 Subject: [PATCH 29/54] [`pylint`] Don't recommend decorating staticmethods with `@singledispatch` (`PLE1519`, `PLE1520`) (#10637) Co-authored-by: Alex Waygood --- .../fixtures/pylint/singledispatch_method.py | 2 +- .../pylint/singledispatchmethod_function.py | 2 +- .../pylint/rules/singledispatch_method.rs | 8 +++-- .../rules/singledispatchmethod_function.rs | 12 +++----- ...sts__PLE1519_singledispatch_method.py.snap | 23 ++++++++++++++- ...1520_singledispatchmethod_function.py.snap | 29 +------------------ 6 files changed, 34 insertions(+), 42 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py b/crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py index 72e813c2df80e..08f1e0c4c608e 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/singledispatch_method.py @@ -20,7 +20,7 @@ def move(self, position): def place(self, position): pass - @singledispatch + @singledispatch # [singledispatch-method] @staticmethod def do(position): pass diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/singledispatchmethod_function.py b/crates/ruff_linter/resources/test/fixtures/pylint/singledispatchmethod_function.py index cf249f184fc83..c4ead1ce41de8 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/singledispatchmethod_function.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/singledispatchmethod_function.py @@ -17,7 +17,7 @@ def convert_position(cls, position): def move(self, position): pass - @singledispatchmethod # [singledispatchmethod-function] + @singledispatchmethod # Ok @staticmethod def do(position): pass diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs index ec732781e3b85..17c24e1a1962f 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatch_method.rs @@ -9,13 +9,13 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; /// ## What it does -/// Checks for `@singledispatch` decorators on class and instance methods. +/// Checks for methods decorated with `@singledispatch`. /// /// ## Why is this bad? /// The `@singledispatch` decorator is intended for use with functions, not methods. /// /// Instead, use the `@singledispatchmethod` decorator, or migrate the method to a -/// standalone function or `@staticmethod`. +/// standalone function. /// /// ## Example /// ```python @@ -88,7 +88,9 @@ pub(crate) fn singledispatch_method( ); if !matches!( type_, - function_type::FunctionType::Method | function_type::FunctionType::ClassMethod + function_type::FunctionType::Method + | function_type::FunctionType::ClassMethod + | function_type::FunctionType::StaticMethod ) { return; } diff --git a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs index 5a60f4b9cf67c..98a05582eb045 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/singledispatchmethod_function.rs @@ -9,12 +9,11 @@ use crate::checkers::ast::Checker; use crate::importer::ImportRequest; /// ## What it does -/// Checks for `@singledispatchmethod` decorators on functions or static -/// methods. +/// Checks for non-method functions decorated with `@singledispatchmethod`. /// /// ## Why is this bad? -/// The `@singledispatchmethod` decorator is intended for use with class and -/// instance methods, not functions. +/// The `@singledispatchmethod` decorator is intended for use with methods, not +/// functions. /// /// Instead, use the `@singledispatch` decorator. /// @@ -85,10 +84,7 @@ pub(crate) fn singledispatchmethod_function( &checker.settings.pep8_naming.classmethod_decorators, &checker.settings.pep8_naming.staticmethod_decorators, ); - if !matches!( - type_, - function_type::FunctionType::Function | function_type::FunctionType::StaticMethod - ) { + if !matches!(type_, function_type::FunctionType::Function) { return; } diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1519_singledispatch_method.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1519_singledispatch_method.py.snap index fc28e7a514995..caa3e35b7c10e 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1519_singledispatch_method.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1519_singledispatch_method.py.snap @@ -40,4 +40,25 @@ singledispatch_method.py:15:5: PLE1519 [*] `@singledispatch` decorator should no 15 |+ @singledispatchmethod # [singledispatch-method] 16 16 | def move(self, position): 17 17 | pass -18 18 | +18 18 | + +singledispatch_method.py:23:5: PLE1519 [*] `@singledispatch` decorator should not be used on methods + | +21 | pass +22 | +23 | @singledispatch # [singledispatch-method] + | ^^^^^^^^^^^^^^^ PLE1519 +24 | @staticmethod +25 | def do(position): + | + = help: Replace with `@singledispatchmethod` + +ℹ Unsafe fix +20 20 | def place(self, position): +21 21 | pass +22 22 | +23 |- @singledispatch # [singledispatch-method] + 23 |+ @singledispatchmethod # [singledispatch-method] +24 24 | @staticmethod +25 25 | def do(position): +26 26 | pass diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1520_singledispatchmethod_function.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1520_singledispatchmethod_function.py.snap index 76b340f38f449..1507083e5817f 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1520_singledispatchmethod_function.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1520_singledispatchmethod_function.py.snap @@ -19,31 +19,4 @@ singledispatchmethod_function.py:4:1: PLE1520 [*] `@singledispatchmethod` decora 4 |+@singledispatch # [singledispatchmethod-function] 5 5 | def convert_position(position): 6 6 | pass -7 7 | - -singledispatchmethod_function.py:20:5: PLE1520 [*] `@singledispatchmethod` decorator should not be used on non-method functions - | -18 | pass -19 | -20 | @singledispatchmethod # [singledispatchmethod-function] - | ^^^^^^^^^^^^^^^^^^^^^ PLE1520 -21 | @staticmethod -22 | def do(position): - | - = help: Replace with `@singledispatch` - -ℹ Unsafe fix -1 |-from functools import singledispatchmethod - 1 |+from functools import singledispatchmethod, singledispatch -2 2 | -3 3 | -4 4 | @singledispatchmethod # [singledispatchmethod-function] --------------------------------------------------------------------------------- -17 17 | def move(self, position): -18 18 | pass -19 19 | -20 |- @singledispatchmethod # [singledispatchmethod-function] - 20 |+ @singledispatch # [singledispatchmethod-function] -21 21 | @staticmethod -22 22 | def do(position): -23 23 | pass +7 7 | From 859e3fc7faaf293097ca1b929004f02089b6d43d Mon Sep 17 00:00:00 2001 From: Aleksei Latyshev Date: Tue, 2 Apr 2024 21:29:42 +0200 Subject: [PATCH 30/54] [`refurb`] Implement `int-on-sliced-str` (`FURB166`) (#10650) ## Summary implement int_on_sliced_str (FURB166) lint - #1348 - [original lint](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/use_int_base_zero.py) ## Test Plan cargo test --- .../resources/test/fixtures/refurb/FURB166.py | 29 ++++ .../src/checkers/ast/analyze/expression.rs | 3 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/refurb/mod.rs | 1 + .../rules/refurb/rules/int_on_sliced_str.rs | 134 +++++++++++++++++ .../ruff_linter/src/rules/refurb/rules/mod.rs | 2 + ...es__refurb__tests__FURB166_FURB166.py.snap | 141 ++++++++++++++++++ ruff.schema.json | 1 + 8 files changed, 312 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/refurb/FURB166.py create mode 100644 crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs create mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB166_FURB166.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB166.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB166.py new file mode 100644 index 0000000000000..1c4764787fb06 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB166.py @@ -0,0 +1,29 @@ +# Errors + +_ = int("0b1010"[2:], 2) +_ = int("0o777"[2:], 8) +_ = int("0xFFFF"[2:], 16) + +b = "0b11" +_ = int(b[2:], 2) + +_ = int("0xFFFF"[2:], base=16) + +_ = int(b"0xFFFF"[2:], 16) + + +def get_str(): + return "0xFFF" + + +_ = int(get_str()[2:], 16) + +# OK + +_ = int("0b1100", 0) +_ = int("123", 3) +_ = int("123", 10) +_ = int("0b1010"[3:], 2) +_ = int("0b1010"[:2], 2) +_ = int("12345"[2:]) +_ = int("12345"[2:], xyz=1) # type: ignore diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index f7fb21373d889..5a64941417858 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -973,6 +973,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) { ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, expr); } + if checker.enabled(Rule::IntOnSlicedStr) { + refurb::rules::int_on_sliced_str(checker, call); + } } Expr::Dict(dict) => { if checker.any_enabled(&[ diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index a6db70d943302..60950ee89288c 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1053,6 +1053,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Refurb, "161") => (RuleGroup::Preview, rules::refurb::rules::BitCount), (Refurb, "163") => (RuleGroup::Preview, rules::refurb::rules::RedundantLogBase), (Refurb, "164") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryFromFloat), + (Refurb, "166") => (RuleGroup::Preview, rules::refurb::rules::IntOnSlicedStr), (Refurb, "167") => (RuleGroup::Preview, rules::refurb::rules::RegexFlagAlias), (Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone), (Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison), diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index 67e1022600426..f27f2291d1135 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -32,6 +32,7 @@ mod tests { #[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))] #[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171.py"))] #[test_case(Rule::BitCount, Path::new("FURB161.py"))] + #[test_case(Rule::IntOnSlicedStr, Path::new("FURB166.py"))] #[test_case(Rule::RegexFlagAlias, Path::new("FURB167.py"))] #[test_case(Rule::IsinstanceTypeNone, Path::new("FURB168.py"))] #[test_case(Rule::TypeNoneComparison, Path::new("FURB169.py"))] diff --git a/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs b/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs new file mode 100644 index 0000000000000..33af2c5ae1d9b --- /dev/null +++ b/crates/ruff_linter/src/rules/refurb/rules/int_on_sliced_str.rs @@ -0,0 +1,134 @@ +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{Expr, ExprCall, Identifier}; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for uses of `int` with an explicit base in which a string expression +/// is stripped of its leading prefix (i.e., `0b`, `0o`, or `0x`). +/// +/// ## Why is this bad? +/// Given an integer string with a prefix (e.g., `0xABC`), Python can automatically +/// determine the base of the integer by the prefix without needing to specify +/// it explicitly. +/// +/// Instead of `int(num[2:], 16)`, use `int(num, 0)`, which will automatically +/// deduce the base based on the prefix. +/// +/// ## Example +/// ```python +/// num = "0xABC" +/// +/// if num.startswith("0b"): +/// i = int(num[2:], 2) +/// elif num.startswith("0o"): +/// i = int(num[2:], 8) +/// elif num.startswith("0x"): +/// i = int(num[2:], 16) +/// +/// print(i) +/// ``` +/// +/// Use instead: +/// ```python +/// num = "0xABC" +/// +/// i = int(num, 0) +/// +/// print(i) +/// ``` +/// +/// ## Fix safety +/// The rule's fix is marked as unsafe, as Ruff cannot guarantee that the +/// argument to `int` will remain valid when its base is included in the +/// function call. +/// +/// ## References +/// - [Python documentation: `int`](https://docs.python.org/3/library/functions.html#int) +#[violation] +pub struct IntOnSlicedStr { + base: u8, +} + +impl AlwaysFixableViolation for IntOnSlicedStr { + #[derive_message_formats] + fn message(&self) -> String { + let IntOnSlicedStr { base } = self; + format!("Use of `int` with explicit `base={base}` after removing prefix") + } + + fn fix_title(&self) -> String { + format!("Replace with `base=0`") + } +} + +pub(crate) fn int_on_sliced_str(checker: &mut Checker, call: &ExprCall) { + // Verify that the function is `int`. + let Expr::Name(name) = call.func.as_ref() else { + return; + }; + if name.id.as_str() != "int" { + return; + } + if !checker.semantic().is_builtin("int") { + return; + } + + // There must be exactly two arguments (e.g., `int(num[2:], 16)`). + let (expression, base) = match ( + call.arguments.args.as_ref(), + call.arguments.keywords.as_ref(), + ) { + ([expression], [base]) if base.arg.as_ref().map(Identifier::as_str) == Some("base") => { + (expression, &base.value) + } + ([expression, base], []) => (expression, base), + _ => { + return; + } + }; + + // The base must be a number literal with a value of 2, 8, or 16. + let Some(base_u8) = base + .as_number_literal_expr() + .and_then(|base| base.value.as_int()) + .and_then(ruff_python_ast::Int::as_u8) + else { + return; + }; + if !matches!(base_u8, 2 | 8 | 16) { + return; + } + + // Determine whether the expression is a slice of a string (e.g., `num[2:]`). + let Expr::Subscript(expr_subscript) = expression else { + return; + }; + let Expr::Slice(expr_slice) = expr_subscript.slice.as_ref() else { + return; + }; + if expr_slice.upper.is_some() || expr_slice.step.is_some() { + return; + } + if !expr_slice + .lower + .as_ref() + .and_then(|expr| expr.as_number_literal_expr()) + .and_then(|expr| expr.value.as_int()) + .is_some_and(|expr| expr.as_u8() == Some(2)) + { + return; + } + + let mut diagnostic = Diagnostic::new(IntOnSlicedStr { base: base_u8 }, call.range()); + diagnostic.set_fix(Fix::unsafe_edits( + Edit::range_replacement( + checker.locator().slice(&*expr_subscript.value).to_string(), + expression.range(), + ), + [Edit::range_replacement("0".to_string(), base.range())], + )); + checker.diagnostics.push(diagnostic); +} diff --git a/crates/ruff_linter/src/rules/refurb/rules/mod.rs b/crates/ruff_linter/src/rules/refurb/rules/mod.rs index cbb8d1fc7f0bb..c18f2350ecd4d 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/mod.rs @@ -5,6 +5,7 @@ pub(crate) use for_loop_set_mutations::*; pub(crate) use hashlib_digest_hex::*; pub(crate) use if_expr_min_max::*; pub(crate) use implicit_cwd::*; +pub(crate) use int_on_sliced_str::*; pub(crate) use isinstance_type_none::*; pub(crate) use list_reverse_copy::*; pub(crate) use math_constant::*; @@ -31,6 +32,7 @@ mod for_loop_set_mutations; mod hashlib_digest_hex; mod if_expr_min_max; mod implicit_cwd; +mod int_on_sliced_str; mod isinstance_type_none; mod list_reverse_copy; mod math_constant; diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB166_FURB166.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB166_FURB166.py.snap new file mode 100644 index 0000000000000..c78dbdb22cad2 --- /dev/null +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB166_FURB166.py.snap @@ -0,0 +1,141 @@ +--- +source: crates/ruff_linter/src/rules/refurb/mod.rs +--- +FURB166.py:3:5: FURB166 [*] Use of `int` with explicit `base=2` after removing prefix + | +1 | # Errors +2 | +3 | _ = int("0b1010"[2:], 2) + | ^^^^^^^^^^^^^^^^^^^^ FURB166 +4 | _ = int("0o777"[2:], 8) +5 | _ = int("0xFFFF"[2:], 16) + | + = help: Replace with `base=0` + +ℹ Unsafe fix +1 1 | # Errors +2 2 | +3 |-_ = int("0b1010"[2:], 2) + 3 |+_ = int("0b1010", 0) +4 4 | _ = int("0o777"[2:], 8) +5 5 | _ = int("0xFFFF"[2:], 16) +6 6 | + +FURB166.py:4:5: FURB166 [*] Use of `int` with explicit `base=8` after removing prefix + | +3 | _ = int("0b1010"[2:], 2) +4 | _ = int("0o777"[2:], 8) + | ^^^^^^^^^^^^^^^^^^^ FURB166 +5 | _ = int("0xFFFF"[2:], 16) + | + = help: Replace with `base=0` + +ℹ Unsafe fix +1 1 | # Errors +2 2 | +3 3 | _ = int("0b1010"[2:], 2) +4 |-_ = int("0o777"[2:], 8) + 4 |+_ = int("0o777", 0) +5 5 | _ = int("0xFFFF"[2:], 16) +6 6 | +7 7 | b = "0b11" + +FURB166.py:5:5: FURB166 [*] Use of `int` with explicit `base=16` after removing prefix + | +3 | _ = int("0b1010"[2:], 2) +4 | _ = int("0o777"[2:], 8) +5 | _ = int("0xFFFF"[2:], 16) + | ^^^^^^^^^^^^^^^^^^^^^ FURB166 +6 | +7 | b = "0b11" + | + = help: Replace with `base=0` + +ℹ Unsafe fix +2 2 | +3 3 | _ = int("0b1010"[2:], 2) +4 4 | _ = int("0o777"[2:], 8) +5 |-_ = int("0xFFFF"[2:], 16) + 5 |+_ = int("0xFFFF", 0) +6 6 | +7 7 | b = "0b11" +8 8 | _ = int(b[2:], 2) + +FURB166.py:8:5: FURB166 [*] Use of `int` with explicit `base=2` after removing prefix + | + 7 | b = "0b11" + 8 | _ = int(b[2:], 2) + | ^^^^^^^^^^^^^ FURB166 + 9 | +10 | _ = int("0xFFFF"[2:], base=16) + | + = help: Replace with `base=0` + +ℹ Unsafe fix +5 5 | _ = int("0xFFFF"[2:], 16) +6 6 | +7 7 | b = "0b11" +8 |-_ = int(b[2:], 2) + 8 |+_ = int(b, 0) +9 9 | +10 10 | _ = int("0xFFFF"[2:], base=16) +11 11 | + +FURB166.py:10:5: FURB166 [*] Use of `int` with explicit `base=16` after removing prefix + | + 8 | _ = int(b[2:], 2) + 9 | +10 | _ = int("0xFFFF"[2:], base=16) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB166 +11 | +12 | _ = int(b"0xFFFF"[2:], 16) + | + = help: Replace with `base=0` + +ℹ Unsafe fix +7 7 | b = "0b11" +8 8 | _ = int(b[2:], 2) +9 9 | +10 |-_ = int("0xFFFF"[2:], base=16) + 10 |+_ = int("0xFFFF", base=0) +11 11 | +12 12 | _ = int(b"0xFFFF"[2:], 16) +13 13 | + +FURB166.py:12:5: FURB166 [*] Use of `int` with explicit `base=16` after removing prefix + | +10 | _ = int("0xFFFF"[2:], base=16) +11 | +12 | _ = int(b"0xFFFF"[2:], 16) + | ^^^^^^^^^^^^^^^^^^^^^^ FURB166 + | + = help: Replace with `base=0` + +ℹ Unsafe fix +9 9 | +10 10 | _ = int("0xFFFF"[2:], base=16) +11 11 | +12 |-_ = int(b"0xFFFF"[2:], 16) + 12 |+_ = int(b"0xFFFF", 0) +13 13 | +14 14 | +15 15 | def get_str(): + +FURB166.py:19:5: FURB166 [*] Use of `int` with explicit `base=16` after removing prefix + | +19 | _ = int(get_str()[2:], 16) + | ^^^^^^^^^^^^^^^^^^^^^^ FURB166 +20 | +21 | # OK + | + = help: Replace with `base=0` + +ℹ Unsafe fix +16 16 | return "0xFFF" +17 17 | +18 18 | +19 |-_ = int(get_str()[2:], 16) + 19 |+_ = int(get_str(), 0) +20 20 | +21 21 | # OK +22 22 | diff --git a/ruff.schema.json b/ruff.schema.json index 1c26154c2d19f..f6d36e1cfc51a 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3063,6 +3063,7 @@ "FURB161", "FURB163", "FURB164", + "FURB166", "FURB167", "FURB168", "FURB169", From dff8f934572af6c6e398bdb8b7dd85914c2b80d7 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 2 Apr 2024 16:18:05 -0400 Subject: [PATCH 31/54] [`flake8-return`] Ignore assignments to annotated variables in `unnecessary-assign` (#10741) ## Summary Closes https://github.com/astral-sh/ruff/issues/10732. --- .../test/fixtures/flake8_return/RET504.py | 15 ++++++++ .../src/rules/flake8_return/rules/function.rs | 6 ++++ ...lake8_return__tests__RET504_RET504.py.snap | 15 ++++++++ .../src/rules/flake8_return/visitor.rs | 36 ++++++++++++------- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py b/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py index 8480aafaa1c01..91a60a7540dc9 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_return/RET504.py @@ -406,3 +406,18 @@ def foo(): with contextlib.suppress(Exception): y = 2 return y + + +# See: https://github.com/astral-sh/ruff/issues/10732 +def func(a: dict[str, int]) -> list[dict[str, int]]: + services: list[dict[str, int]] + if "services" in a: + services = a["services"] + return services + + +# See: https://github.com/astral-sh/ruff/issues/10732 +def func(a: dict[str, int]) -> list[dict[str, int]]: + if "services" in a: + services = a["services"] + return services diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index 9e0f90c4077e4..124abdfee953e 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -567,6 +567,12 @@ fn unnecessary_assign(checker: &mut Checker, stack: &Stack) { continue; } + // Ignore variables that have an annotation defined elsewhere. + if stack.annotations.contains(assigned_id.as_str()) { + continue; + } + + // Ignore `nonlocal` and `global` variables. if stack.non_locals.contains(assigned_id.as_str()) { continue; } diff --git a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap b/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap index 3656e71afbd2d..46ffb2df68b29 100644 --- a/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap +++ b/crates/ruff_linter/src/rules/flake8_return/snapshots/ruff_linter__rules__flake8_return__tests__RET504_RET504.py.snap @@ -241,4 +241,19 @@ RET504.py:400:12: RET504 [*] Unnecessary assignment to `y` before `return` state 402 401 | 403 402 | def foo(): +RET504.py:423:16: RET504 [*] Unnecessary assignment to `services` before `return` statement + | +421 | if "services" in a: +422 | services = a["services"] +423 | return services + | ^^^^^^^^ RET504 + | + = help: Remove unnecessary assignment +ℹ Unsafe fix +419 419 | # See: https://github.com/astral-sh/ruff/issues/10732 +420 420 | def func(a: dict[str, int]) -> list[dict[str, int]]: +421 421 | if "services" in a: +422 |- services = a["services"] +423 |- return services + 422 |+ return a["services"] diff --git a/crates/ruff_linter/src/rules/flake8_return/visitor.rs b/crates/ruff_linter/src/rules/flake8_return/visitor.rs index f583c22d6cab0..c88abc6676bd5 100644 --- a/crates/ruff_linter/src/rules/flake8_return/visitor.rs +++ b/crates/ruff_linter/src/rules/flake8_return/visitor.rs @@ -13,6 +13,21 @@ pub(super) struct Stack<'data> { pub(super) elifs_elses: Vec<(&'data [Stmt], &'data ElifElseClause)>, /// The non-local variables in the current function. pub(super) non_locals: FxHashSet<&'data str>, + /// The annotated variables in the current function. + /// + /// For example, consider: + /// ```python + /// x: int + /// + /// if True: + /// x = foo() + /// return x + /// ``` + /// + /// In this case, the annotation on `x` is used to cast the return value + /// of `foo()` to an `int`. Removing the `x = foo()` statement would + /// change the return type of the function. + pub(super) annotations: FxHashSet<&'data str>, /// Whether the current function is a generator. pub(super) is_generator: bool, /// The `assignment`-to-`return` statement pairs in the current function. @@ -86,6 +101,14 @@ impl<'semantic, 'a> Visitor<'a> for ReturnVisitor<'semantic, 'a> { .non_locals .extend(names.iter().map(Identifier::as_str)); } + Stmt::AnnAssign(ast::StmtAnnAssign { target, value, .. }) => { + // Ex) `x: int` + if value.is_none() { + if let Expr::Name(name) = target.as_ref() { + self.stack.annotations.insert(name.id.as_str()); + } + } + } Stmt::Return(stmt_return) => { // If the `return` statement is preceded by an `assignment` statement, then the // `assignment` statement may be redundant. @@ -163,19 +186,6 @@ impl<'semantic, 'a> Visitor<'a> for ReturnVisitor<'semantic, 'a> { } } -/// RET504 -/// If the last statement is a `return` statement, and the second-to-last statement is a -/// `with` statement that suppresses an exception, then we should not analyze the `return` -/// statement for unnecessary assignments. Otherwise we will suggest removing the assignment -/// and the `with` statement, which would change the behavior of the code. -/// -/// Example: -/// ```python -/// def foo(data): -/// with suppress(JSONDecoderError): -/// data = data.decode() -/// return data - /// Returns `true` if the [`With`] statement is known to have a conditional body. In other words: /// if the [`With`] statement's body may or may not run. /// From 2a4084a2bb7b339fe3c888e48bbb2f5d6e01093d Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 2 Apr 2024 17:06:21 -0600 Subject: [PATCH 32/54] chore(docs): update ruff_linter crate name in CONTRIBUTING.md (#10745) Reading through `CONTRIBUTING.md`, I happened to notice that it still referred to the `ruff_linter` crate as the `ruff` crate. `ruff` is a different crate, located in `crates/ruff`, which doesn't contain "the vast majority of the code and all the lint rules." --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7eca0a04cd72f..c4def89956e56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,8 +123,8 @@ prior to merging. Ruff is structured as a monorepo with a [flat crate structure](https://matklad.github.io/2021/08/22/large-rust-workspaces.html), such that all crates are contained in a flat `crates` directory. -The vast majority of the code, including all lint rules, lives in the `ruff` crate (located at -`crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you. +The vast majority of the code, including all lint rules, lives in the `ruff_linter` crate (located +at `crates/ruff_linter`). As a contributor, that's the crate that'll be most relevant to you. At the time of writing, the repository includes the following crates: From 814b26f82e1e6a678dc59ee4cd0fc1c277efb517 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 2 Apr 2024 17:42:55 -0600 Subject: [PATCH 33/54] [flake8_comprehensions] update docs for unnecessary-comprehension-any-all (C419) (#10744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref #3259; see in particular https://github.com/astral-sh/ruff/issues/3259#issuecomment-2033127339 ## Summary Improve the accuracy of the docs for this lint rule/fix. ## Test Plan Generated the docs locally and visited the page for this rule: ![Screenshot 2024-04-02 at 4 56 40 PM](https://github.com/astral-sh/ruff/assets/61586/64f25cf6-edfe-4682-ac8e-7e21b834f5f2) --------- Co-authored-by: Zanie Blue --- .../unnecessary_comprehension_any_all.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_any_all.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_any_all.rs index a713059f3d15c..de538c6f8f184 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_any_all.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_any_all.rs @@ -15,11 +15,11 @@ use crate::rules::flake8_comprehensions::fixes; /// /// ## Why is this bad? /// `any` and `all` take any iterators, including generators. Converting a generator to a list -/// by way of a list comprehension is unnecessary and reduces performance due to the -/// overhead of creating the list. +/// by way of a list comprehension is unnecessary and requires iterating all values, even if `any` +/// or `all` could short-circuit early. /// /// For example, compare the performance of `all` with a list comprehension against that -/// of a generator (~40x faster here): +/// of a generator in a case where an early short-circuit is possible (almost 40x faster): /// /// ```console /// In [1]: %timeit all([i for i in range(1000)]) @@ -29,6 +29,12 @@ use crate::rules::flake8_comprehensions::fixes; /// 212 ns ± 0.892 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each) /// ``` /// +/// This performance difference is due to short-circuiting; if the entire iterable has to be +/// traversed, the comprehension version may even be a bit faster (list allocation overhead is not +/// necessarily greater than generator overhead). +/// +/// The generator version is more memory-efficient. +/// /// ## Examples /// ```python /// any([x.id for x in bar]) @@ -42,9 +48,10 @@ use crate::rules::flake8_comprehensions::fixes; /// ``` /// /// ## Fix safety -/// This rule's fix is marked as unsafe, as it may occasionally drop comments -/// when rewriting the comprehension. In most cases, though, comments will be -/// preserved. +/// This rule's fix is marked as unsafe, as it can change the behavior of the code if the iteration +/// has side effects (due to laziness and short-circuiting). The fix may also drop comments when +/// rewriting some comprehensions. +/// #[violation] pub struct UnnecessaryComprehensionAnyAll; From e54b591ec72380e9f88d907c2fde847202cbd0ac Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 2 Apr 2024 22:10:04 -0400 Subject: [PATCH 34/54] Use section name range for all section-related docstring diagnostics (#10740) ## Summary We may not have had access to this in the past, but in short, if the diagnostic is related to a specific section of a docstring, it seems better to highlight the section (via the header) than the _entire_ docstring. This should be completely compatible with existing `# noqa` since it's always inside of a multi-line string anyway, and in such cases the `# noqa` is always placed at the end of the multiline string. Closes https://github.com/astral-sh/ruff/issues/10736. --- .../src/rules/pydocstyle/rules/sections.rs | 182 ++++--- ...ydocstyle__tests__D214_D214_module.py.snap | 56 +-- ...__pydocstyle__tests__D214_sections.py.snap | 40 +- ...ules__pydocstyle__tests__D215_D215.py.snap | 14 +- ...__pydocstyle__tests__D215_sections.py.snap | 36 +- ...__pydocstyle__tests__D405_sections.py.snap | 47 +- ...__pydocstyle__tests__D406_sections.py.snap | 67 +-- ...__pydocstyle__tests__D407_sections.py.snap | 471 ++++++------------ ...__pydocstyle__tests__D408_sections.py.snap | 29 +- ...__pydocstyle__tests__D409_sections.py.snap | 72 +-- ...ules__pydocstyle__tests__D410_D410.py.snap | 51 +- ...__pydocstyle__tests__D410_sections.py.snap | 55 +- ...__pydocstyle__tests__D411_sections.py.snap | 72 +-- ...__pydocstyle__tests__D412_sections.py.snap | 30 +- ...ules__pydocstyle__tests__D413_D413.py.snap | 76 +-- ...__pydocstyle__tests__D413_sections.py.snap | 134 ++--- ...__pydocstyle__tests__D414_sections.py.snap | 136 ++--- 17 files changed, 559 insertions(+), 1009 deletions(-) diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs index c9074b377eb42..dbe02ee66be8c 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs @@ -2,6 +2,7 @@ use itertools::Itertools; use once_cell::sync::Lazy; use regex::Regex; use rustc_hash::FxHashSet; +use std::ops::Add; use ruff_diagnostics::{AlwaysFixableViolation, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix}; @@ -10,8 +11,8 @@ use ruff_python_ast::docstrings::{clean_space, leading_space}; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::ParameterWithDefault; use ruff_python_semantic::analyze::visibility::is_staticmethod; -use ruff_python_trivia::{textwrap::dedent, PythonWhitespace}; -use ruff_source_file::NewlineWithTrailingNewline; +use ruff_python_trivia::{textwrap::dedent, Cursor}; +use ruff_source_file::{Line, NewlineWithTrailingNewline}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::checkers::ast::Checker; @@ -1377,50 +1378,41 @@ fn blanks_and_section_underline( } if let Some(non_blank_line) = following_lines.next() { - let dash_line_found = is_dashed_underline(&non_blank_line); - - if dash_line_found { + if let Some(dashed_line) = find_underline(&non_blank_line, '-') { if blank_lines_after_header > 0 { if checker.enabled(Rule::SectionUnderlineAfterName) { let mut diagnostic = Diagnostic::new( SectionUnderlineAfterName { name: context.section_name().to_string(), }, - docstring.range(), + dashed_line, ); - let range = TextRange::new(context.following_range().start(), blank_lines_end); + // Delete any blank lines between the header and the underline. - diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); + diagnostic.set_fix(Fix::safe_edit(Edit::deletion( + context.following_range().start(), + blank_lines_end, + ))); + checker.diagnostics.push(diagnostic); } } - if non_blank_line - .trim() - .chars() - .filter(|char| *char == '-') - .count() - != context.section_name().len() - { + if dashed_line.len().to_usize() != context.section_name().len() { if checker.enabled(Rule::SectionUnderlineMatchesSectionLength) { let mut diagnostic = Diagnostic::new( SectionUnderlineMatchesSectionLength { name: context.section_name().to_string(), }, - docstring.range(), + dashed_line, ); + // Replace the existing underline with a line of the appropriate length. - let content = format!( - "{}{}{}", - clean_space(docstring.indentation), + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( "-".repeat(context.section_name().len()), - checker.stylist().line_ending().as_str() - ); - diagnostic.set_fix(Fix::safe_edit(Edit::replacement( - content, - blank_lines_end, - non_blank_line.full_end(), + dashed_line, ))); + checker.diagnostics.push(diagnostic); } } @@ -1432,8 +1424,9 @@ fn blanks_and_section_underline( SectionUnderlineNotOverIndented { name: context.section_name().to_string(), }, - docstring.range(), + dashed_line, ); + // Replace the existing indentation with whitespace of the appropriate length. let range = TextRange::at( blank_lines_end, @@ -1445,6 +1438,7 @@ fn blanks_and_section_underline( } else { Edit::range_replacement(contents, range) })); + checker.diagnostics.push(diagnostic); } } @@ -1467,7 +1461,7 @@ fn blanks_and_section_underline( EmptyDocstringSection { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), )); } } else if checker.enabled(Rule::BlankLinesBetweenHeaderAndContent) { @@ -1475,7 +1469,7 @@ fn blanks_and_section_underline( BlankLinesBetweenHeaderAndContent { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), ); // Delete any blank lines between the header and content. diagnostic.set_fix(Fix::safe_edit(Edit::deletion( @@ -1491,47 +1485,50 @@ fn blanks_and_section_underline( EmptyDocstringSection { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), )); } } } else { - let equal_line_found = non_blank_line - .chars() - .all(|char| char.is_whitespace() || char == '='); - if checker.enabled(Rule::DashedUnderlineAfterSection) { - let mut diagnostic = Diagnostic::new( - DashedUnderlineAfterSection { - name: context.section_name().to_string(), - }, - docstring.range(), - ); - // Add a dashed line (of the appropriate length) under the section header. - let content = format!( - "{}{}{}", - checker.stylist().line_ending().as_str(), - clean_space(docstring.indentation), - "-".repeat(context.section_name().len()), - ); - if equal_line_found - && non_blank_line.trim_whitespace().len() == context.section_name().len() - { + if let Some(equal_line) = find_underline(&non_blank_line, '=') { + let mut diagnostic = Diagnostic::new( + DashedUnderlineAfterSection { + name: context.section_name().to_string(), + }, + equal_line, + ); + // If an existing underline is an equal sign line of the appropriate length, // replace it with a dashed line. - diagnostic.set_fix(Fix::safe_edit(Edit::replacement( - content, - context.summary_range().end(), - non_blank_line.end(), + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + "-".repeat(context.section_name().len()), + equal_line, ))); + + checker.diagnostics.push(diagnostic); } else { - // Otherwise, insert a dashed line after the section header. + let mut diagnostic = Diagnostic::new( + DashedUnderlineAfterSection { + name: context.section_name().to_string(), + }, + context.section_name_range(), + ); + + // Add a dashed line (of the appropriate length) under the section header. + let content = format!( + "{}{}{}", + checker.stylist().line_ending().as_str(), + clean_space(docstring.indentation), + "-".repeat(context.section_name().len()), + ); diagnostic.set_fix(Fix::safe_edit(Edit::insertion( content, context.summary_range().end(), ))); + + checker.diagnostics.push(diagnostic); } - checker.diagnostics.push(diagnostic); } if blank_lines_after_header > 0 { if checker.enabled(Rule::BlankLinesBetweenHeaderAndContent) { @@ -1539,7 +1536,7 @@ fn blanks_and_section_underline( BlankLinesBetweenHeaderAndContent { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), ); let range = TextRange::new(context.following_range().start(), blank_lines_end); // Delete any blank lines between the header and content. @@ -1548,16 +1545,16 @@ fn blanks_and_section_underline( } } } - } - // Nothing but blank lines after the section header. - else { + } else { + // Nothing but blank lines after the section header. if checker.enabled(Rule::DashedUnderlineAfterSection) { let mut diagnostic = Diagnostic::new( DashedUnderlineAfterSection { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), ); + // Add a dashed line (of the appropriate length) under the section header. let content = format!( "{}{}{}", @@ -1565,11 +1562,11 @@ fn blanks_and_section_underline( clean_space(docstring.indentation), "-".repeat(context.section_name().len()), ); - diagnostic.set_fix(Fix::safe_edit(Edit::insertion( content, context.summary_range().end(), ))); + checker.diagnostics.push(diagnostic); } if checker.enabled(Rule::EmptyDocstringSection) { @@ -1577,7 +1574,7 @@ fn blanks_and_section_underline( EmptyDocstringSection { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), )); } } @@ -1592,15 +1589,15 @@ fn common_section( if checker.enabled(Rule::CapitalizeSectionName) { let capitalized_section_name = context.kind().as_str(); if context.section_name() != capitalized_section_name { + let section_range = context.section_name_range(); let mut diagnostic = Diagnostic::new( CapitalizeSectionName { name: context.section_name().to_string(), }, - docstring.range(), + section_range, ); // Replace the section title with the capitalized variant. This requires // locating the start and end of the section name. - let section_range = context.section_name_range(); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( capitalized_section_name.to_string(), section_range, @@ -1612,16 +1609,17 @@ fn common_section( if checker.enabled(Rule::SectionNotOverIndented) { let leading_space = leading_space(context.summary_line()); if leading_space.len() > docstring.indentation.len() { + let section_range = context.section_name_range(); let mut diagnostic = Diagnostic::new( SectionNotOverIndented { name: context.section_name().to_string(), }, - docstring.range(), + section_range, ); + // Replace the existing indentation with whitespace of the appropriate length. let content = clean_space(docstring.indentation); let fix_range = TextRange::at(context.start(), leading_space.text_len()); - diagnostic.set_fix(Fix::safe_edit(if content.is_empty() { Edit::range_deletion(fix_range) } else { @@ -1641,11 +1639,12 @@ fn common_section( .take_while(|line| line.trim().is_empty()) .count(); if num_blank_lines < 2 { + let section_range = context.section_name_range(); let mut diagnostic = Diagnostic::new( NoBlankLineAfterSection { name: context.section_name().to_string(), }, - docstring.range(), + section_range, ); // Add a newline at the beginning of the next section. diagnostic.set_fix(Fix::safe_edit(Edit::insertion( @@ -1682,11 +1681,12 @@ fn common_section( context.end(), ); + let section_range = context.section_name_range(); let mut diagnostic = Diagnostic::new( BlankLineAfterLastSection { name: context.section_name().to_string(), }, - docstring.range(), + section_range, ); diagnostic.set_fix(Fix::safe_edit(edit)); checker.diagnostics.push(diagnostic); @@ -1699,11 +1699,12 @@ fn common_section( .previous_line() .is_some_and(|line| line.trim().is_empty()) { + let section_range = context.section_name_range(); let mut diagnostic = Diagnostic::new( NoBlankLineBeforeSection { name: context.section_name().to_string(), }, - docstring.range(), + section_range, ); // Add a blank line before the section. diagnostic.set_fix(Fix::safe_edit(Edit::insertion( @@ -1800,10 +1801,11 @@ fn args_section(context: &SectionContext) -> FxHashSet { let leading_space = leading_space(first_line.as_str()); let relevant_lines = std::iter::once(first_line) .chain(following_lines) - .map(|l| l.as_str()) .filter(|line| { - line.is_empty() || (line.starts_with(leading_space) && !is_dashed_underline(line)) + line.is_empty() + || (line.starts_with(leading_space) && find_underline(line, '-').is_none()) }) + .map(|line| line.as_str()) .join("\n"); let args_content = dedent(&relevant_lines); @@ -1897,7 +1899,7 @@ fn numpy_section( NewLineAfterSectionName { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), ); let section_range = context.section_name_range(); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(TextRange::at( @@ -1931,7 +1933,7 @@ fn google_section( SectionNameEndsInColon { name: context.section_name().to_string(), }, - docstring.range(), + context.section_name_range(), ); // Replace the suffix. let section_name_range = context.section_name_range(); @@ -1991,7 +1993,35 @@ fn parse_google_sections( } } -fn is_dashed_underline(line: &str) -> bool { - let trimmed_line = line.trim(); - !trimmed_line.is_empty() && trimmed_line.chars().all(|char| char == '-') +/// Returns the [`TextRange`] of the underline, if a line consists of only dashes. +fn find_underline(line: &Line, dash: char) -> Option { + let mut cursor = Cursor::new(line.as_str()); + + // Eat leading whitespace. + cursor.eat_while(char::is_whitespace); + + // Determine the start of the dashes. + let offset = cursor.token_len(); + + // Consume the dashes. + cursor.start_token(); + cursor.eat_while(|c| c == dash); + + // Determine the end of the dashes. + let len = cursor.token_len(); + + // If there are no dashes, return None. + if len == TextSize::new(0) { + return None; + } + + // Eat trailing whitespace. + cursor.eat_while(char::is_whitespace); + + // If there are any characters after the dashes, return None. + if !cursor.is_eof() { + return None; + } + + Some(TextRange::at(offset, len).add(line.start())) } diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_D214_module.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_D214_module.py.snap index 13c7174f438ef..c95abffdad67b 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_D214_module.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_D214_module.py.snap @@ -1,23 +1,16 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -D214_module.py:1:1: D214 [*] Section is over-indented ("Returns") - | - 1 | / """A module docstring with D214 violations - 2 | | - 3 | | Returns - 4 | | ----- - 5 | | valid returns - 6 | | - 7 | | Args - 8 | | ----- - 9 | | valid args -10 | | """ - | |___^ D214 -11 | -12 | import os - | - = help: Remove over-indentation from "Returns" +D214_module.py:3:5: D214 [*] Section is over-indented ("Returns") + | +1 | """A module docstring with D214 violations +2 | +3 | Returns + | ^^^^^^^ D214 +4 | ----- +5 | valid returns + | + = help: Remove over-indentation from "Returns" ℹ Safe fix 1 1 | """A module docstring with D214 violations @@ -28,23 +21,16 @@ D214_module.py:1:1: D214 [*] Section is over-indented ("Returns") 5 5 | valid returns 6 6 | -D214_module.py:1:1: D214 [*] Section is over-indented ("Args") - | - 1 | / """A module docstring with D214 violations - 2 | | - 3 | | Returns - 4 | | ----- - 5 | | valid returns - 6 | | - 7 | | Args - 8 | | ----- - 9 | | valid args -10 | | """ - | |___^ D214 -11 | -12 | import os - | - = help: Remove over-indentation from "Args" +D214_module.py:7:5: D214 [*] Section is over-indented ("Args") + | +5 | valid returns +6 | +7 | Args + | ^^^^ D214 +8 | ----- +9 | valid args + | + = help: Remove over-indentation from "Args" ℹ Safe fix 4 4 | ----- @@ -55,5 +41,3 @@ D214_module.py:1:1: D214 [*] Section is over-indented ("Args") 8 8 | ----- 9 9 | valid args 10 10 | """ - - diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap index d8ce888327b5c..89f47fa2c67b6 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D214_sections.py.snap @@ -1,19 +1,14 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:144:5: D214 [*] Section is over-indented ("Returns") +sections.py:146:9: D214 [*] Section is over-indented ("Returns") | -142 | @expect("D214: Section is over-indented ('Returns')") -143 | def section_overindented(): # noqa: D416 -144 | """Toggle the gizmo. - | _____^ -145 | | -146 | | Returns -147 | | ------- -148 | | A value of some sort. -149 | | -150 | | """ - | |_______^ D214 +144 | """Toggle the gizmo. +145 | +146 | Returns + | ^^^^^^^ D214 +147 | ------- +148 | A value of some sort. | = help: Remove over-indentation from "Returns" @@ -27,18 +22,13 @@ sections.py:144:5: D214 [*] Section is over-indented ("Returns") 148 148 | A value of some sort. 149 149 | -sections.py:558:5: D214 [*] Section is over-indented ("Returns") +sections.py:563:9: D214 [*] Section is over-indented ("Returns") | -557 | def titlecase_sub_section_header(): -558 | """Below, `Returns:` should be considered a section header. - | _____^ -559 | | -560 | | Args: -561 | | Here's a note. -562 | | -563 | | Returns: -564 | | """ - | |_______^ D214 +561 | Here's a note. +562 | +563 | Returns: + | ^^^^^^^ D214 +564 | """ | = help: Remove over-indentation from "Returns" @@ -50,6 +40,4 @@ sections.py:558:5: D214 [*] Section is over-indented ("Returns") 563 |+ Returns: 564 564 | """ 565 565 | -566 566 | - - +566 566 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_D215.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_D215.py.snap index 813615fb5e180..b93074fda8c98 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_D215.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_D215.py.snap @@ -1,13 +1,13 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -D215.py:1:1: D215 [*] Section underline is over-indented ("TODO") +D215.py:3:5: D215 [*] Section underline is over-indented ("TODO") | -1 | / """ -2 | | TODO: -3 | | - -4 | | """ - | |___^ D215 +1 | """ +2 | TODO: +3 | - + | ^ D215 +4 | """ | = help: Remove over-indentation from "TODO" underline @@ -17,5 +17,3 @@ D215.py:1:1: D215 [*] Section underline is over-indented ("TODO") 3 |- - 3 |+ 4 4 | """ - - diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_sections.py.snap index 7ff2484755e39..afbd747fbd769 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D215_sections.py.snap @@ -1,19 +1,12 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:156:5: D215 [*] Section underline is over-indented ("Returns") +sections.py:159:9: D215 [*] Section underline is over-indented ("Returns") | -154 | @expect("D215: Section underline is over-indented (in section 'Returns')") -155 | def section_underline_overindented(): # noqa: D416 -156 | """Toggle the gizmo. - | _____^ -157 | | -158 | | Returns -159 | | ------- -160 | | A value of some sort. -161 | | -162 | | """ - | |_______^ D215 +158 | Returns +159 | ------- + | ^^^^^^^ D215 +160 | A value of some sort. | = help: Remove over-indentation from "Returns" underline @@ -27,17 +20,12 @@ sections.py:156:5: D215 [*] Section underline is over-indented ("Returns") 161 161 | 162 162 | """ -sections.py:170:5: D215 [*] Section underline is over-indented ("Returns") +sections.py:173:9: D215 [*] Section underline is over-indented ("Returns") | -168 | @expect("D414: Section has no content ('Returns')") -169 | def section_underline_overindented_and_contentless(): # noqa: D416 -170 | """Toggle the gizmo. - | _____^ -171 | | -172 | | Returns -173 | | ------- -174 | | """ - | |_______^ D215 +172 | Returns +173 | ------- + | ^^^^^^^ D215 +174 | """ | = help: Remove over-indentation from "Returns" underline @@ -49,6 +37,4 @@ sections.py:170:5: D215 [*] Section underline is over-indented ("Returns") 173 |+ ------ 174 174 | """ 175 175 | -176 176 | - - +176 176 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D405_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D405_sections.py.snap index 3ad8fb7ea6b84..2aff3caaa0427 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D405_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D405_sections.py.snap @@ -1,19 +1,14 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:17:5: D405 [*] Section name should be properly capitalized ("returns") +sections.py:19:5: D405 [*] Section name should be properly capitalized ("returns") | -15 | "('Returns', not 'returns')") -16 | def not_capitalized(): # noqa: D416 -17 | """Toggle the gizmo. - | _____^ -18 | | -19 | | returns -20 | | ------- -21 | | A value of some sort. -22 | | -23 | | """ - | |_______^ D405 +17 | """Toggle the gizmo. +18 | +19 | returns + | ^^^^^^^ D405 +20 | ------- +21 | A value of some sort. | = help: Capitalize "returns" @@ -27,27 +22,13 @@ sections.py:17:5: D405 [*] Section name should be properly capitalized ("returns 21 21 | A value of some sort. 22 22 | -sections.py:216:5: D405 [*] Section name should be properly capitalized ("Short summary") +sections.py:218:5: D405 [*] Section name should be properly capitalized ("Short summary") | -214 | @expect("D407: Missing dashed underline after section ('Raises')") -215 | def multiple_sections(): # noqa: D416 -216 | """Toggle the gizmo. - | _____^ -217 | | -218 | | Short summary -219 | | ------------- -220 | | -221 | | This is the function's description, which will also specify what it -222 | | returns. -223 | | -224 | | Returns -225 | | ------ -226 | | Many many wonderful things. -227 | | Raises: -228 | | My attention. -229 | | -230 | | """ - | |_______^ D405 +216 | """Toggle the gizmo. +217 | +218 | Short summary + | ^^^^^^^^^^^^^ D405 +219 | ------------- | = help: Capitalize "Short summary" @@ -60,5 +41,3 @@ sections.py:216:5: D405 [*] Section name should be properly capitalized ("Short 219 219 | ------------- 220 220 | 221 221 | This is the function's description, which will also specify what it - - diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap index 14530459e76bd..b64a36dc51e68 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D406_sections.py.snap @@ -1,19 +1,14 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:30:5: D406 [*] Section name should end with a newline ("Returns") +sections.py:32:5: D406 [*] Section name should end with a newline ("Returns") | -28 | "('Returns', not 'Returns:')") -29 | def superfluous_suffix(): # noqa: D416 -30 | """Toggle the gizmo. - | _____^ -31 | | -32 | | Returns: -33 | | ------- -34 | | A value of some sort. -35 | | -36 | | """ - | |_______^ D406 +30 | """Toggle the gizmo. +31 | +32 | Returns: + | ^^^^^^^ D406 +33 | ------- +34 | A value of some sort. | = help: Add newline after "Returns" @@ -27,27 +22,13 @@ sections.py:30:5: D406 [*] Section name should end with a newline ("Returns") 34 34 | A value of some sort. 35 35 | -sections.py:216:5: D406 [*] Section name should end with a newline ("Raises") +sections.py:227:5: D406 [*] Section name should end with a newline ("Raises") | -214 | @expect("D407: Missing dashed underline after section ('Raises')") -215 | def multiple_sections(): # noqa: D416 -216 | """Toggle the gizmo. - | _____^ -217 | | -218 | | Short summary -219 | | ------------- -220 | | -221 | | This is the function's description, which will also specify what it -222 | | returns. -223 | | -224 | | Returns -225 | | ------ -226 | | Many many wonderful things. -227 | | Raises: -228 | | My attention. -229 | | -230 | | """ - | |_______^ D406 +225 | ------ +226 | Many many wonderful things. +227 | Raises: + | ^^^^^^ D406 +228 | My attention. | = help: Add newline after "Raises" @@ -61,20 +42,14 @@ sections.py:216:5: D406 [*] Section name should end with a newline ("Raises") 229 229 | 230 230 | """ -sections.py:588:5: D406 [*] Section name should end with a newline ("Parameters") +sections.py:590:5: D406 [*] Section name should end with a newline ("Parameters") | -587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213 -588 | """Test that lower case subsection header is valid even if it has the same name as section kind. - | _____^ -589 | | -590 | | Parameters: -591 | | ---------- -592 | | parameters: -593 | | A list of string parameters -594 | | value: -595 | | Some value -596 | | """ - | |_______^ D406 +588 | """Test that lower case subsection header is valid even if it has the same name as section kind. +589 | +590 | Parameters: + | ^^^^^^^^^^ D406 +591 | ---------- +592 | parameters: | = help: Add newline after "Parameters" @@ -87,5 +62,3 @@ sections.py:588:5: D406 [*] Section name should end with a newline ("Parameters" 591 591 | ---------- 592 592 | parameters: 593 593 | A list of string parameters - - diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap index 95efe19803e69..61fe5b4c6da24 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D407_sections.py.snap @@ -1,18 +1,13 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:42:5: D407 [*] Missing dashed underline after section ("Returns") +sections.py:44:5: D407 [*] Missing dashed underline after section ("Returns") | -40 | @expect("D407: Missing dashed underline after section ('Returns')") -41 | def no_underline(): # noqa: D416 -42 | """Toggle the gizmo. - | _____^ -43 | | -44 | | Returns -45 | | A value of some sort. -46 | | -47 | | """ - | |_______^ D407 +42 | """Toggle the gizmo. +43 | +44 | Returns + | ^^^^^^^ D407 +45 | A value of some sort. | = help: Add dashed line under "Returns" @@ -25,17 +20,14 @@ sections.py:42:5: D407 [*] Missing dashed underline after section ("Returns") 46 47 | 47 48 | """ -sections.py:54:5: D407 [*] Missing dashed underline after section ("Returns") +sections.py:56:5: D407 [*] Missing dashed underline after section ("Returns") | -52 | @expect("D414: Section has no content ('Returns')") -53 | def no_underline_and_no_description(): # noqa: D416 -54 | """Toggle the gizmo. - | _____^ -55 | | -56 | | Returns -57 | | -58 | | """ - | |_______^ D407 +54 | """Toggle the gizmo. +55 | +56 | Returns + | ^^^^^^^ D407 +57 | +58 | """ | = help: Add dashed line under "Returns" @@ -48,15 +40,12 @@ sections.py:54:5: D407 [*] Missing dashed underline after section ("Returns") 58 59 | """ 59 60 | -sections.py:65:5: D407 [*] Missing dashed underline after section ("Returns") +sections.py:67:5: D407 [*] Missing dashed underline after section ("Returns") | -63 | @expect("D414: Section has no content ('Returns')") -64 | def no_underline_and_no_newline(): # noqa: D416 -65 | """Toggle the gizmo. - | _____^ -66 | | -67 | | Returns""" - | |______________^ D407 +65 | """Toggle the gizmo. +66 | +67 | Returns""" + | ^^^^^^^ D407 | = help: Add dashed line under "Returns" @@ -71,27 +60,13 @@ sections.py:65:5: D407 [*] Missing dashed underline after section ("Returns") 69 70 | 70 71 | @expect(_D213) -sections.py:216:5: D407 [*] Missing dashed underline after section ("Raises") - | -214 | @expect("D407: Missing dashed underline after section ('Raises')") -215 | def multiple_sections(): # noqa: D416 -216 | """Toggle the gizmo. - | _____^ -217 | | -218 | | Short summary -219 | | ------------- -220 | | -221 | | This is the function's description, which will also specify what it -222 | | returns. -223 | | -224 | | Returns -225 | | ------ -226 | | Many many wonderful things. -227 | | Raises: -228 | | My attention. -229 | | -230 | | """ - | |_______^ D407 +sections.py:227:5: D407 [*] Missing dashed underline after section ("Raises") + | +225 | ------ +226 | Many many wonderful things. +227 | Raises: + | ^^^^^^ D407 +228 | My attention. | = help: Add dashed line under "Raises" @@ -104,23 +79,13 @@ sections.py:216:5: D407 [*] Missing dashed underline after section ("Raises") 229 230 | 230 231 | """ -sections.py:261:5: D407 [*] Missing dashed underline after section ("Args") - | -259 | @expect("D414: Section has no content ('Returns')") -260 | def valid_google_style_section(): # noqa: D406, D407 -261 | """Toggle the gizmo. - | _____^ -262 | | -263 | | Args: -264 | | note: A random string. -265 | | -266 | | Returns: -267 | | -268 | | Raises: -269 | | RandomError: A random error that occurs randomly. -270 | | -271 | | """ - | |_______^ D407 +sections.py:263:5: D407 [*] Missing dashed underline after section ("Args") + | +261 | """Toggle the gizmo. +262 | +263 | Args: + | ^^^^ D407 +264 | note: A random string. | = help: Add dashed line under "Args" @@ -133,23 +98,14 @@ sections.py:261:5: D407 [*] Missing dashed underline after section ("Args") 265 266 | 266 267 | Returns: -sections.py:261:5: D407 [*] Missing dashed underline after section ("Returns") - | -259 | @expect("D414: Section has no content ('Returns')") -260 | def valid_google_style_section(): # noqa: D406, D407 -261 | """Toggle the gizmo. - | _____^ -262 | | -263 | | Args: -264 | | note: A random string. -265 | | -266 | | Returns: -267 | | -268 | | Raises: -269 | | RandomError: A random error that occurs randomly. -270 | | -271 | | """ - | |_______^ D407 +sections.py:266:5: D407 [*] Missing dashed underline after section ("Returns") + | +264 | note: A random string. +265 | +266 | Returns: + | ^^^^^^^ D407 +267 | +268 | Raises: | = help: Add dashed line under "Returns" @@ -162,23 +118,13 @@ sections.py:261:5: D407 [*] Missing dashed underline after section ("Returns") 268 269 | Raises: 269 270 | RandomError: A random error that occurs randomly. -sections.py:261:5: D407 [*] Missing dashed underline after section ("Raises") - | -259 | @expect("D414: Section has no content ('Returns')") -260 | def valid_google_style_section(): # noqa: D406, D407 -261 | """Toggle the gizmo. - | _____^ -262 | | -263 | | Args: -264 | | note: A random string. -265 | | -266 | | Returns: -267 | | -268 | | Raises: -269 | | RandomError: A random error that occurs randomly. -270 | | -271 | | """ - | |_______^ D407 +sections.py:268:5: D407 [*] Missing dashed underline after section ("Raises") + | +266 | Returns: +267 | +268 | Raises: + | ^^^^^^ D407 +269 | RandomError: A random error that occurs randomly. | = help: Add dashed line under "Raises" @@ -191,18 +137,13 @@ sections.py:261:5: D407 [*] Missing dashed underline after section ("Raises") 270 271 | 271 272 | """ -sections.py:278:5: D407 [*] Missing dashed underline after section ("Args") +sections.py:280:5: D407 [*] Missing dashed underline after section ("Args") | -276 | "('Args:', not 'Args')") -277 | def missing_colon_google_style_section(): # noqa: D406, D407 -278 | """Toggle the gizmo. - | _____^ -279 | | -280 | | Args -281 | | note: A random string. -282 | | -283 | | """ - | |_______^ D407 +278 | """Toggle the gizmo. +279 | +280 | Args + | ^^^^ D407 +281 | note: A random string. | = help: Add dashed line under "Args" @@ -215,21 +156,14 @@ sections.py:278:5: D407 [*] Missing dashed underline after section ("Args") 282 283 | 283 284 | """ -sections.py:293:9: D407 [*] Missing dashed underline after section ("Args") - | -292 | def bar(y=2): # noqa: D207, D213, D406, D407 -293 | """Nested function test for docstrings. - | _________^ -294 | | -295 | | Will this work when referencing x? -296 | | -297 | | Args: -298 | | x: Test something -299 | | that is broken. -300 | | -301 | | """ - | |___________^ D407 -302 | print(x) +sections.py:297:9: D407 [*] Missing dashed underline after section ("Args") + | +295 | Will this work when referencing x? +296 | +297 | Args: + | ^^^^ D407 +298 | x: Test something +299 | that is broken. | = help: Add dashed line under "Args" @@ -242,18 +176,13 @@ sections.py:293:9: D407 [*] Missing dashed underline after section ("Args") 299 300 | that is broken. 300 301 | -sections.py:310:5: D407 [*] Missing dashed underline after section ("Args") +sections.py:312:5: D407 [*] Missing dashed underline after section ("Args") | -308 | "'test_missing_google_args' docstring)") -309 | def test_missing_google_args(x=1, y=2, _private=3): # noqa: D406, D407 -310 | """Toggle the gizmo. - | _____^ -311 | | -312 | | Args: -313 | | x (int): The greatest integer. -314 | | -315 | | """ - | |_______^ D407 +310 | """Toggle the gizmo. +311 | +312 | Args: + | ^^^^ D407 +313 | x (int): The greatest integer. | = help: Add dashed line under "Args" @@ -266,20 +195,14 @@ sections.py:310:5: D407 [*] Missing dashed underline after section ("Args") 314 315 | 315 316 | """ -sections.py:322:9: D407 [*] Missing dashed underline after section ("Args") - | -321 | def test_method(self, test, another_test, _): # noqa: D213, D407 -322 | """Test a valid args section. - | _________^ -323 | | -324 | | Args: -325 | | test: A parameter. -326 | | another_test: Another parameter. -327 | | -328 | | """ - | |___________^ D407 -329 | -330 | @expect("D417: Missing argument descriptions in the docstring " +sections.py:324:9: D407 [*] Missing dashed underline after section ("Args") + | +322 | """Test a valid args section. +323 | +324 | Args: + | ^^^^ D407 +325 | test: A parameter. +326 | another_test: Another parameter. | = help: Add dashed line under "Args" @@ -292,20 +215,13 @@ sections.py:322:9: D407 [*] Missing dashed underline after section ("Args") 326 327 | another_test: Another parameter. 327 328 | -sections.py:334:9: D407 [*] Missing dashed underline after section ("Args") - | -332 | "'test_missing_args' docstring)", arg_count=5) -333 | def test_missing_args(self, test, x, y, z=3, _private_arg=3): # noqa: D213, D407 -334 | """Test a valid args section. - | _________^ -335 | | -336 | | Args: -337 | | x: Another parameter. -338 | | -339 | | """ - | |___________^ D407 -340 | -341 | @classmethod +sections.py:336:9: D407 [*] Missing dashed underline after section ("Args") + | +334 | """Test a valid args section. +335 | +336 | Args: + | ^^^^ D407 +337 | x: Another parameter. | = help: Add dashed line under "Args" @@ -318,21 +234,14 @@ sections.py:334:9: D407 [*] Missing dashed underline after section ("Args") 338 339 | 339 340 | """ -sections.py:346:9: D407 [*] Missing dashed underline after section ("Args") - | -344 | "'test_missing_args_class_method' docstring)", arg_count=5) -345 | def test_missing_args_class_method(cls, test, x, y, _, z=3): # noqa: D213, D407 -346 | """Test a valid args section. - | _________^ -347 | | -348 | | Args: -349 | | x: Another parameter. The parameter below is missing description. -350 | | y: -351 | | -352 | | """ - | |___________^ D407 -353 | -354 | @staticmethod +sections.py:348:9: D407 [*] Missing dashed underline after section ("Args") + | +346 | """Test a valid args section. +347 | +348 | Args: + | ^^^^ D407 +349 | x: Another parameter. The parameter below is missing description. +350 | y: | = help: Add dashed line under "Args" @@ -345,20 +254,13 @@ sections.py:346:9: D407 [*] Missing dashed underline after section ("Args") 350 351 | y: 351 352 | -sections.py:359:9: D407 [*] Missing dashed underline after section ("Args") - | -357 | "'test_missing_args_static_method' docstring)", arg_count=4) -358 | def test_missing_args_static_method(a, x, y, _test, z=3): # noqa: D213, D407 -359 | """Test a valid args section. - | _________^ -360 | | -361 | | Args: -362 | | x: Another parameter. -363 | | -364 | | """ - | |___________^ D407 -365 | -366 | @staticmethod +sections.py:361:9: D407 [*] Missing dashed underline after section ("Args") + | +359 | """Test a valid args section. +360 | +361 | Args: + | ^^^^ D407 +362 | x: Another parameter. | = help: Add dashed line under "Args" @@ -371,20 +273,13 @@ sections.py:359:9: D407 [*] Missing dashed underline after section ("Args") 363 364 | 364 365 | """ -sections.py:371:9: D407 [*] Missing dashed underline after section ("Args") - | -369 | "'test_missing_docstring' docstring)", arg_count=2) -370 | def test_missing_docstring(a, b): # noqa: D213, D407 -371 | """Test a valid args section. - | _________^ -372 | | -373 | | Args: -374 | | a: -375 | | -376 | | """ - | |___________^ D407 -377 | -378 | @staticmethod +sections.py:373:9: D407 [*] Missing dashed underline after section ("Args") + | +371 | """Test a valid args section. +372 | +373 | Args: + | ^^^^ D407 +374 | a: | = help: Add dashed line under "Args" @@ -397,24 +292,14 @@ sections.py:371:9: D407 [*] Missing dashed underline after section ("Args") 375 376 | 376 377 | """ -sections.py:380:9: D407 [*] Missing dashed underline after section ("Args") - | -378 | @staticmethod -379 | def test_hanging_indent(skip, verbose): # noqa: D213, D407 -380 | """Do stuff. - | _________^ -381 | | -382 | | Args: -383 | | skip (:attr:`.Skip`): -384 | | Lorem ipsum dolor sit amet, consectetur adipiscing elit. -385 | | Etiam at tellus a tellus faucibus maximus. Curabitur tellus -386 | | mauris, semper id vehicula ac, feugiat ut tortor. -387 | | verbose (bool): -388 | | If True, print out as much information as possible. -389 | | If False, print out concise "one-liner" information. -390 | | -391 | | """ - | |___________^ D407 +sections.py:382:9: D407 [*] Missing dashed underline after section ("Args") + | +380 | """Do stuff. +381 | +382 | Args: + | ^^^^ D407 +383 | skip (:attr:`.Skip`): +384 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. | = help: Add dashed line under "Args" @@ -427,20 +312,13 @@ sections.py:380:9: D407 [*] Missing dashed underline after section ("Args") 384 385 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 385 386 | Etiam at tellus a tellus faucibus maximus. Curabitur tellus -sections.py:499:9: D407 [*] Missing dashed underline after section ("Args") - | -497 | "'test_incorrect_indent' docstring)", arg_count=3) -498 | def test_incorrect_indent(self, x=1, y=2): # noqa: D207, D213, D407 -499 | """Reproducing issue #437. - | _________^ -500 | | -501 | | Testing this incorrectly indented docstring. -502 | | -503 | | Args: -504 | | x: Test argument. -505 | | -506 | | """ - | |___________^ D407 +sections.py:503:9: D407 [*] Missing dashed underline after section ("Args") + | +501 | Testing this incorrectly indented docstring. +502 | +503 | Args: + | ^^^^ D407 +504 | x: Test argument. | = help: Add dashed line under "Args" @@ -453,16 +331,12 @@ sections.py:499:9: D407 [*] Missing dashed underline after section ("Args") 505 506 | 506 507 | """ -sections.py:519:5: D407 [*] Missing dashed underline after section ("Parameters") +sections.py:522:5: D407 [*] Missing dashed underline after section ("Parameters") | -518 | def replace_equals_with_dash(): -519 | """Equal length equals should be replaced with dashes. - | _____^ -520 | | -521 | | Parameters -522 | | ========== -523 | | """ - | |_______^ D407 +521 | Parameters +522 | ========== + | ^^^^^^^^^^ D407 +523 | """ | = help: Add dashed line under "Parameters" @@ -476,16 +350,12 @@ sections.py:519:5: D407 [*] Missing dashed underline after section ("Parameters" 524 524 | 525 525 | -sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters") +sections.py:530:5: D407 [*] Missing dashed underline after section ("Parameters") | -526 | def replace_equals_with_dash2(): -527 | """Here, the length of equals is not the same. - | _____^ -528 | | -529 | | Parameters -530 | | =========== -531 | | """ - | |_______^ D407 +529 | Parameters +530 | =========== + | ^^^^^^^^^^^ D407 +531 | """ | = help: Add dashed line under "Parameters" @@ -493,23 +363,19 @@ sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters" 527 527 | """Here, the length of equals is not the same. 528 528 | 529 529 | Parameters +530 |- =========== 530 |+ ---------- -530 531 | =========== -531 532 | """ -532 533 | - -sections.py:548:5: D407 [*] Missing dashed underline after section ("Args") - | -547 | def lowercase_sub_section_header(): -548 | """Below, `returns:` should _not_ be considered a section header. - | _____^ -549 | | -550 | | Args: -551 | | Here's a note. -552 | | -553 | | returns: -554 | | """ - | |_______^ D407 +531 531 | """ +532 532 | +533 533 | + +sections.py:550:5: D407 [*] Missing dashed underline after section ("Args") + | +548 | """Below, `returns:` should _not_ be considered a section header. +549 | +550 | Args: + | ^^^^ D407 +551 | Here's a note. | = help: Add dashed line under "Args" @@ -522,18 +388,13 @@ sections.py:548:5: D407 [*] Missing dashed underline after section ("Args") 552 553 | 553 554 | returns: -sections.py:558:5: D407 [*] Missing dashed underline after section ("Args") +sections.py:560:5: D407 [*] Missing dashed underline after section ("Args") | -557 | def titlecase_sub_section_header(): -558 | """Below, `Returns:` should be considered a section header. - | _____^ -559 | | -560 | | Args: -561 | | Here's a note. -562 | | -563 | | Returns: -564 | | """ - | |_______^ D407 +558 | """Below, `Returns:` should be considered a section header. +559 | +560 | Args: + | ^^^^ D407 +561 | Here's a note. | = help: Add dashed line under "Args" @@ -546,18 +407,13 @@ sections.py:558:5: D407 [*] Missing dashed underline after section ("Args") 562 563 | 563 564 | Returns: -sections.py:558:5: D407 [*] Missing dashed underline after section ("Returns") +sections.py:563:9: D407 [*] Missing dashed underline after section ("Returns") | -557 | def titlecase_sub_section_header(): -558 | """Below, `Returns:` should be considered a section header. - | _____^ -559 | | -560 | | Args: -561 | | Here's a note. -562 | | -563 | | Returns: -564 | | """ - | |_______^ D407 +561 | Here's a note. +562 | +563 | Returns: + | ^^^^^^^ D407 +564 | """ | = help: Add dashed line under "Returns" @@ -570,19 +426,14 @@ sections.py:558:5: D407 [*] Missing dashed underline after section ("Returns") 565 566 | 566 567 | -sections.py:600:4: D407 [*] Missing dashed underline after section ("Parameters") +sections.py:602:4: D407 [*] Missing dashed underline after section ("Parameters") | -599 | def test_lowercase_sub_section_header_different_kind(returns: int): -600 | """Test that lower case subsection header is valid even if it is of a different kind. - | ____^ -601 | | -602 | | Parameters -603 | | -‐----------------- -604 | | returns: -605 | | some value -606 | | -607 | | """ - | |______^ D407 +600 | """Test that lower case subsection header is valid even if it is of a different kind. +601 | +602 | Parameters + | ^^^^^^^^^^ D407 +603 | -‐----------------- +604 | returns: | = help: Add dashed line under "Parameters" @@ -594,5 +445,3 @@ sections.py:600:4: D407 [*] Missing dashed underline after section ("Parameters" 603 604 | -‐----------------- 604 605 | returns: 605 606 | some value - - diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D408_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D408_sections.py.snap index 8ee2ee32490da..38bec4612b427 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D408_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D408_sections.py.snap @@ -1,22 +1,15 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:94:5: D408 [*] Section underline should be in the line following the section's name ("Returns") - | - 92 | "section's name ('Returns')") - 93 | def blank_line_before_underline(): # noqa: D416 - 94 | """Toggle the gizmo. - | _____^ - 95 | | - 96 | | Returns - 97 | | - 98 | | ------- - 99 | | A value of some sort. -100 | | -101 | | """ - | |_______^ D408 - | - = help: Add underline to "Returns" +sections.py:98:5: D408 [*] Section underline should be in the line following the section's name ("Returns") + | +96 | Returns +97 | +98 | ------- + | ^^^^^^^ D408 +99 | A value of some sort. + | + = help: Add underline to "Returns" ℹ Safe fix 94 94 | """Toggle the gizmo. @@ -25,6 +18,4 @@ sections.py:94:5: D408 [*] Section underline should be in the line following the 97 |- 98 97 | ------- 99 98 | A value of some sort. -100 99 | - - +100 99 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap index 881b5a0f0f099..1b59fad56deee 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D409_sections.py.snap @@ -1,19 +1,12 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:108:5: D409 [*] Section underline should match the length of its name ("Returns") +sections.py:111:5: D409 [*] Section underline should match the length of its name ("Returns") | -106 | "(Expected 7 dashes in section 'Returns', got 2)") -107 | def bad_underline_length(): # noqa: D416 -108 | """Toggle the gizmo. - | _____^ -109 | | -110 | | Returns -111 | | -- -112 | | A value of some sort. -113 | | -114 | | """ - | |_______^ D409 +110 | Returns +111 | -- + | ^^ D409 +112 | A value of some sort. | = help: Adjust underline length to match "Returns" @@ -27,27 +20,13 @@ sections.py:108:5: D409 [*] Section underline should match the length of its nam 113 113 | 114 114 | """ -sections.py:216:5: D409 [*] Section underline should match the length of its name ("Returns") +sections.py:225:5: D409 [*] Section underline should match the length of its name ("Returns") | -214 | @expect("D407: Missing dashed underline after section ('Raises')") -215 | def multiple_sections(): # noqa: D416 -216 | """Toggle the gizmo. - | _____^ -217 | | -218 | | Short summary -219 | | ------------- -220 | | -221 | | This is the function's description, which will also specify what it -222 | | returns. -223 | | -224 | | Returns -225 | | ------ -226 | | Many many wonderful things. -227 | | Raises: -228 | | My attention. -229 | | -230 | | """ - | |_______^ D409 +224 | Returns +225 | ------ + | ^^^^^^ D409 +226 | Many many wonderful things. +227 | Raises: | = help: Adjust underline length to match "Returns" @@ -61,28 +40,13 @@ sections.py:216:5: D409 [*] Section underline should match the length of its nam 227 227 | Raises: 228 228 | My attention. -sections.py:568:5: D409 [*] Section underline should match the length of its name ("Other Parameters") +sections.py:578:5: D409 [*] Section underline should match the length of its name ("Other Parameters") | -567 | def test_method_should_be_correctly_capitalized(parameters: list[str], other_parameters: dict[str, str]): # noqa: D213 -568 | """Test parameters and attributes sections are capitalized correctly. - | _____^ -569 | | -570 | | Parameters -571 | | ---------- -572 | | parameters: -573 | | A list of string parameters -574 | | other_parameters: -575 | | A dictionary of string attributes -576 | | -577 | | Other Parameters -578 | | ---------- -579 | | other_parameters: -580 | | A dictionary of string attributes -581 | | parameters: -582 | | A list of string parameters -583 | | -584 | | """ - | |_______^ D409 +577 | Other Parameters +578 | ---------- + | ^^^^^^^^^^ D409 +579 | other_parameters: +580 | A dictionary of string attributes | = help: Adjust underline length to match "Other Parameters" @@ -95,5 +59,3 @@ sections.py:568:5: D409 [*] Section underline should match the length of its nam 579 579 | other_parameters: 580 580 | A dictionary of string attributes 581 581 | parameters: - - diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_D410.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_D410.py.snap index 7631ab868735c..2f7b0e4f92977 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_D410.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_D410.py.snap @@ -1,27 +1,16 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -D410.py:2:5: D410 [*] Missing blank line after section ("Parameters") - | - 1 | def f(a: int, b: int) -> int: - 2 | """Showcase function. - | _____^ - 3 | | - 4 | | Parameters - 5 | | ---------- - 6 | | a : int - 7 | | _description_ - 8 | | b : int - 9 | | _description_ -10 | | Returns -11 | | ------- -12 | | int -13 | | _description -14 | | """ - | |_______^ D410 -15 | return b - a - | - = help: Add blank line after "Parameters" +D410.py:4:5: D410 [*] Missing blank line after section ("Parameters") + | +2 | """Showcase function. +3 | +4 | Parameters + | ^^^^^^^^^^ D410 +5 | ---------- +6 | a : int + | + = help: Add blank line after "Parameters" ℹ Safe fix 7 7 | _description_ @@ -32,18 +21,14 @@ D410.py:2:5: D410 [*] Missing blank line after section ("Parameters") 11 12 | ------- 12 13 | int -D410.py:19:5: D410 [*] Missing blank line after section ("Parameters") +D410.py:21:5: D410 [*] Missing blank line after section ("Parameters") | -18 | def f() -> int: -19 | """Showcase function. - | _____^ -20 | | -21 | | Parameters -22 | | ---------- -23 | | Returns -24 | | ------- -25 | | """ - | |_______^ D410 +19 | """Showcase function. +20 | +21 | Parameters + | ^^^^^^^^^^ D410 +22 | ---------- +23 | Returns | = help: Add blank line after "Parameters" @@ -55,5 +40,3 @@ D410.py:19:5: D410 [*] Missing blank line after section ("Parameters") 23 24 | Returns 24 25 | ------- 25 26 | """ - - diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_sections.py.snap index 00d15acee3dfc..e8c466e1387ca 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D410_sections.py.snap @@ -1,24 +1,14 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:76:5: D410 [*] Missing blank line after section ("Returns") +sections.py:78:5: D410 [*] Missing blank line after section ("Returns") | -74 | @expect("D414: Section has no content ('Yields')") -75 | def consecutive_sections(): # noqa: D416 -76 | """Toggle the gizmo. - | _____^ -77 | | -78 | | Returns -79 | | ------- -80 | | Yields -81 | | ------ -82 | | -83 | | Raises -84 | | ------ -85 | | Questions. -86 | | -87 | | """ - | |_______^ D410 +76 | """Toggle the gizmo. +77 | +78 | Returns + | ^^^^^^^ D410 +79 | ------- +80 | Yields | = help: Add blank line after "Returns" @@ -31,27 +21,14 @@ sections.py:76:5: D410 [*] Missing blank line after section ("Returns") 81 82 | ------ 82 83 | -sections.py:216:5: D410 [*] Missing blank line after section ("Returns") +sections.py:224:5: D410 [*] Missing blank line after section ("Returns") | -214 | @expect("D407: Missing dashed underline after section ('Raises')") -215 | def multiple_sections(): # noqa: D416 -216 | """Toggle the gizmo. - | _____^ -217 | | -218 | | Short summary -219 | | ------------- -220 | | -221 | | This is the function's description, which will also specify what it -222 | | returns. -223 | | -224 | | Returns -225 | | ------ -226 | | Many many wonderful things. -227 | | Raises: -228 | | My attention. -229 | | -230 | | """ - | |_______^ D410 +222 | returns. +223 | +224 | Returns + | ^^^^^^^ D410 +225 | ------ +226 | Many many wonderful things. | = help: Add blank line after "Returns" @@ -62,6 +39,4 @@ sections.py:216:5: D410 [*] Missing blank line after section ("Returns") 227 |+ 227 228 | Raises: 228 229 | My attention. -229 230 | - - +229 230 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D411_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D411_sections.py.snap index f93054611928b..b516028f32fe3 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D411_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D411_sections.py.snap @@ -1,24 +1,13 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:76:5: D411 [*] Missing blank line before section ("Yields") +sections.py:80:5: D411 [*] Missing blank line before section ("Yields") | -74 | @expect("D414: Section has no content ('Yields')") -75 | def consecutive_sections(): # noqa: D416 -76 | """Toggle the gizmo. - | _____^ -77 | | -78 | | Returns -79 | | ------- -80 | | Yields -81 | | ------ -82 | | -83 | | Raises -84 | | ------ -85 | | Questions. -86 | | -87 | | """ - | |_______^ D411 +78 | Returns +79 | ------- +80 | Yields + | ^^^^^^ D411 +81 | ------ | = help: Add blank line before "Yields" @@ -31,20 +20,13 @@ sections.py:76:5: D411 [*] Missing blank line before section ("Yields") 81 82 | ------ 82 83 | -sections.py:131:5: D411 [*] Missing blank line before section ("Returns") +sections.py:134:5: D411 [*] Missing blank line before section ("Returns") | -129 | @expect("D411: Missing blank line before section ('Returns')") -130 | def no_blank_line_before_section(): # noqa: D416 -131 | """Toggle the gizmo. - | _____^ -132 | | -133 | | The function's description. -134 | | Returns -135 | | ------- -136 | | A value of some sort. -137 | | -138 | | """ - | |_______^ D411 +133 | The function's description. +134 | Returns + | ^^^^^^^ D411 +135 | ------- +136 | A value of some sort. | = help: Add blank line before "Returns" @@ -57,27 +39,13 @@ sections.py:131:5: D411 [*] Missing blank line before section ("Returns") 135 136 | ------- 136 137 | A value of some sort. -sections.py:216:5: D411 [*] Missing blank line before section ("Raises") +sections.py:227:5: D411 [*] Missing blank line before section ("Raises") | -214 | @expect("D407: Missing dashed underline after section ('Raises')") -215 | def multiple_sections(): # noqa: D416 -216 | """Toggle the gizmo. - | _____^ -217 | | -218 | | Short summary -219 | | ------------- -220 | | -221 | | This is the function's description, which will also specify what it -222 | | returns. -223 | | -224 | | Returns -225 | | ------ -226 | | Many many wonderful things. -227 | | Raises: -228 | | My attention. -229 | | -230 | | """ - | |_______^ D411 +225 | ------ +226 | Many many wonderful things. +227 | Raises: + | ^^^^^^ D411 +228 | My attention. | = help: Add blank line before "Raises" @@ -88,6 +56,4 @@ sections.py:216:5: D411 [*] Missing blank line before section ("Raises") 227 |+ 227 228 | Raises: 228 229 | My attention. -229 230 | - - +229 230 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D412_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D412_sections.py.snap index a3861a422ee2a..a6461d2987661 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D412_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D412_sections.py.snap @@ -1,27 +1,13 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:216:5: D412 [*] No blank lines allowed between a section header and its content ("Short summary") +sections.py:218:5: D412 [*] No blank lines allowed between a section header and its content ("Short summary") | -214 | @expect("D407: Missing dashed underline after section ('Raises')") -215 | def multiple_sections(): # noqa: D416 -216 | """Toggle the gizmo. - | _____^ -217 | | -218 | | Short summary -219 | | ------------- -220 | | -221 | | This is the function's description, which will also specify what it -222 | | returns. -223 | | -224 | | Returns -225 | | ------ -226 | | Many many wonderful things. -227 | | Raises: -228 | | My attention. -229 | | -230 | | """ - | |_______^ D412 +216 | """Toggle the gizmo. +217 | +218 | Short summary + | ^^^^^^^^^^^^^ D412 +219 | ------------- | = help: Remove blank line(s) @@ -32,6 +18,4 @@ sections.py:216:5: D412 [*] No blank lines allowed between a section header and 220 |- 221 220 | This is the function's description, which will also specify what it 222 221 | returns. -223 222 | - - +223 222 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_D413.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_D413.py.snap index c04db39b79078..a2a831d93e752 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_D413.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_D413.py.snap @@ -1,18 +1,14 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -D413.py:1:1: D413 [*] Missing blank line after last section ("Returns") +D413.py:7:1: D413 [*] Missing blank line after last section ("Returns") | -1 | / """Do something. -2 | | -3 | | Args: -4 | | x: the value -5 | | with a hanging indent -6 | | -7 | | Returns: -8 | | the value -9 | | """ - | |___^ D413 +5 | with a hanging indent +6 | +7 | Returns: + | ^^^^^^^ D413 +8 | the value +9 | """ | = help: Add blank line after "Returns" @@ -25,20 +21,14 @@ D413.py:1:1: D413 [*] Missing blank line after last section ("Returns") 10 11 | 11 12 | -D413.py:13:5: D413 [*] Missing blank line after last section ("Returns") +D413.py:19:5: D413 [*] Missing blank line after last section ("Returns") | -12 | def func(): -13 | """Do something. - | _____^ -14 | | -15 | | Args: -16 | | x: the value -17 | | with a hanging indent -18 | | -19 | | Returns: -20 | | the value -21 | | """ - | |_______^ D413 +17 | with a hanging indent +18 | +19 | Returns: + | ^^^^^^^ D413 +20 | the value +21 | """ | = help: Add blank line after "Returns" @@ -51,19 +41,13 @@ D413.py:13:5: D413 [*] Missing blank line after last section ("Returns") 22 23 | 23 24 | -D413.py:52:5: D413 [*] Missing blank line after last section ("Returns") +D413.py:58:5: D413 [*] Missing blank line after last section ("Returns") | -51 | def func(): -52 | """Do something. - | _____^ -53 | | -54 | | Args: -55 | | x: the value -56 | | with a hanging indent -57 | | -58 | | Returns: -59 | | the value""" - | |____________________^ D413 +56 | with a hanging indent +57 | +58 | Returns: + | ^^^^^^^ D413 +59 | the value""" | = help: Add blank line after "Returns" @@ -79,20 +63,14 @@ D413.py:52:5: D413 [*] Missing blank line after last section ("Returns") 61 63 | 62 64 | def func(): -D413.py:63:5: D413 [*] Missing blank line after last section ("Returns") +D413.py:69:5: D413 [*] Missing blank line after last section ("Returns") | -62 | def func(): -63 | """Do something. - | _____^ -64 | | -65 | | Args: -66 | | x: the value -67 | | with a hanging indent -68 | | -69 | | Returns: -70 | | the value -71 | | """ - | |___________^ D413 +67 | with a hanging indent +68 | +69 | Returns: + | ^^^^^^^ D413 +70 | the value +71 | """ | = help: Add blank line after "Returns" diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap index 3618cdd6ca061..223c7c38218af 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D413_sections.py.snap @@ -1,15 +1,12 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:65:5: D413 [*] Missing blank line after last section ("Returns") +sections.py:67:5: D413 [*] Missing blank line after last section ("Returns") | -63 | @expect("D414: Section has no content ('Returns')") -64 | def no_underline_and_no_newline(): # noqa: D416 -65 | """Toggle the gizmo. - | _____^ -66 | | -67 | | Returns""" - | |______________^ D413 +65 | """Toggle the gizmo. +66 | +67 | Returns""" + | ^^^^^^^ D413 | = help: Add blank line after "Returns" @@ -25,18 +22,14 @@ sections.py:65:5: D413 [*] Missing blank line after last section ("Returns") 69 71 | 70 72 | @expect(_D213) -sections.py:120:5: D413 [*] Missing blank line after last section ("Returns") +sections.py:122:5: D413 [*] Missing blank line after last section ("Returns") | -118 | @expect("D413: Missing blank line after last section ('Returns')") -119 | def no_blank_line_after_last_section(): # noqa: D416 -120 | """Toggle the gizmo. - | _____^ -121 | | -122 | | Returns -123 | | ------- -124 | | A value of some sort. -125 | | """ - | |_______^ D413 +120 | """Toggle the gizmo. +121 | +122 | Returns + | ^^^^^^^ D413 +123 | ------- +124 | A value of some sort. | = help: Add blank line after "Returns" @@ -49,17 +42,14 @@ sections.py:120:5: D413 [*] Missing blank line after last section ("Returns") 126 127 | 127 128 | -sections.py:170:5: D413 [*] Missing blank line after last section ("Returns") +sections.py:172:5: D413 [*] Missing blank line after last section ("Returns") | -168 | @expect("D414: Section has no content ('Returns')") -169 | def section_underline_overindented_and_contentless(): # noqa: D416 -170 | """Toggle the gizmo. - | _____^ -171 | | -172 | | Returns -173 | | ------- -174 | | """ - | |_______^ D413 +170 | """Toggle the gizmo. +171 | +172 | Returns + | ^^^^^^^ D413 +173 | ------- +174 | """ | = help: Add blank line after "Returns" @@ -72,16 +62,14 @@ sections.py:170:5: D413 [*] Missing blank line after last section ("Returns") 175 176 | 176 177 | -sections.py:519:5: D413 [*] Missing blank line after last section ("Parameters") +sections.py:521:5: D413 [*] Missing blank line after last section ("Parameters") | -518 | def replace_equals_with_dash(): -519 | """Equal length equals should be replaced with dashes. - | _____^ -520 | | -521 | | Parameters -522 | | ========== -523 | | """ - | |_______^ D413 +519 | """Equal length equals should be replaced with dashes. +520 | +521 | Parameters + | ^^^^^^^^^^ D413 +522 | ========== +523 | """ | = help: Add blank line after "Parameters" @@ -94,16 +82,14 @@ sections.py:519:5: D413 [*] Missing blank line after last section ("Parameters") 524 525 | 525 526 | -sections.py:527:5: D413 [*] Missing blank line after last section ("Parameters") +sections.py:529:5: D413 [*] Missing blank line after last section ("Parameters") | -526 | def replace_equals_with_dash2(): -527 | """Here, the length of equals is not the same. - | _____^ -528 | | -529 | | Parameters -530 | | =========== -531 | | """ - | |_______^ D413 +527 | """Here, the length of equals is not the same. +528 | +529 | Parameters + | ^^^^^^^^^^ D413 +530 | =========== +531 | """ | = help: Add blank line after "Parameters" @@ -116,18 +102,13 @@ sections.py:527:5: D413 [*] Missing blank line after last section ("Parameters") 532 533 | 533 534 | -sections.py:548:5: D413 [*] Missing blank line after last section ("Args") +sections.py:550:5: D413 [*] Missing blank line after last section ("Args") | -547 | def lowercase_sub_section_header(): -548 | """Below, `returns:` should _not_ be considered a section header. - | _____^ -549 | | -550 | | Args: -551 | | Here's a note. -552 | | -553 | | returns: -554 | | """ - | |_______^ D413 +548 | """Below, `returns:` should _not_ be considered a section header. +549 | +550 | Args: + | ^^^^ D413 +551 | Here's a note. | = help: Add blank line after "Args" @@ -140,18 +121,13 @@ sections.py:548:5: D413 [*] Missing blank line after last section ("Args") 555 556 | 556 557 | -sections.py:558:5: D413 [*] Missing blank line after last section ("Returns") +sections.py:563:9: D413 [*] Missing blank line after last section ("Returns") | -557 | def titlecase_sub_section_header(): -558 | """Below, `Returns:` should be considered a section header. - | _____^ -559 | | -560 | | Args: -561 | | Here's a note. -562 | | -563 | | Returns: -564 | | """ - | |_______^ D413 +561 | Here's a note. +562 | +563 | Returns: + | ^^^^^^^ D413 +564 | """ | = help: Add blank line after "Returns" @@ -164,20 +140,14 @@ sections.py:558:5: D413 [*] Missing blank line after last section ("Returns") 565 566 | 566 567 | -sections.py:588:5: D413 [*] Missing blank line after last section ("Parameters") +sections.py:590:5: D413 [*] Missing blank line after last section ("Parameters") | -587 | def test_lowercase_sub_section_header_should_be_valid(parameters: list[str], value: int): # noqa: D213 -588 | """Test that lower case subsection header is valid even if it has the same name as section kind. - | _____^ -589 | | -590 | | Parameters: -591 | | ---------- -592 | | parameters: -593 | | A list of string parameters -594 | | value: -595 | | Some value -596 | | """ - | |_______^ D413 +588 | """Test that lower case subsection header is valid even if it has the same name as section kind. +589 | +590 | Parameters: + | ^^^^^^^^^^ D413 +591 | ---------- +592 | parameters: | = help: Add blank line after "Parameters" diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D414_sections.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D414_sections.py.snap index 9566fd691a6d4..b4a8317bd9295 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D414_sections.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D414_sections.py.snap @@ -1,114 +1,68 @@ --- source: crates/ruff_linter/src/rules/pydocstyle/mod.rs --- -sections.py:54:5: D414 Section has no content ("Returns") +sections.py:56:5: D414 Section has no content ("Returns") | -52 | @expect("D414: Section has no content ('Returns')") -53 | def no_underline_and_no_description(): # noqa: D416 -54 | """Toggle the gizmo. - | _____^ -55 | | -56 | | Returns -57 | | -58 | | """ - | |_______^ D414 +54 | """Toggle the gizmo. +55 | +56 | Returns + | ^^^^^^^ D414 +57 | +58 | """ | -sections.py:65:5: D414 Section has no content ("Returns") +sections.py:67:5: D414 Section has no content ("Returns") | -63 | @expect("D414: Section has no content ('Returns')") -64 | def no_underline_and_no_newline(): # noqa: D416 -65 | """Toggle the gizmo. - | _____^ -66 | | -67 | | Returns""" - | |______________^ D414 +65 | """Toggle the gizmo. +66 | +67 | Returns""" + | ^^^^^^^ D414 | -sections.py:76:5: D414 Section has no content ("Returns") +sections.py:78:5: D414 Section has no content ("Returns") | -74 | @expect("D414: Section has no content ('Yields')") -75 | def consecutive_sections(): # noqa: D416 -76 | """Toggle the gizmo. - | _____^ -77 | | -78 | | Returns -79 | | ------- -80 | | Yields -81 | | ------ -82 | | -83 | | Raises -84 | | ------ -85 | | Questions. -86 | | -87 | | """ - | |_______^ D414 +76 | """Toggle the gizmo. +77 | +78 | Returns + | ^^^^^^^ D414 +79 | ------- +80 | Yields | -sections.py:76:5: D414 Section has no content ("Yields") +sections.py:80:5: D414 Section has no content ("Yields") | -74 | @expect("D414: Section has no content ('Yields')") -75 | def consecutive_sections(): # noqa: D416 -76 | """Toggle the gizmo. - | _____^ -77 | | -78 | | Returns -79 | | ------- -80 | | Yields -81 | | ------ -82 | | -83 | | Raises -84 | | ------ -85 | | Questions. -86 | | -87 | | """ - | |_______^ D414 +78 | Returns +79 | ------- +80 | Yields + | ^^^^^^ D414 +81 | ------ | -sections.py:170:5: D414 Section has no content ("Returns") +sections.py:172:5: D414 Section has no content ("Returns") | -168 | @expect("D414: Section has no content ('Returns')") -169 | def section_underline_overindented_and_contentless(): # noqa: D416 -170 | """Toggle the gizmo. - | _____^ -171 | | -172 | | Returns -173 | | ------- -174 | | """ - | |_______^ D414 +170 | """Toggle the gizmo. +171 | +172 | Returns + | ^^^^^^^ D414 +173 | ------- +174 | """ | -sections.py:261:5: D414 Section has no content ("Returns") +sections.py:266:5: D414 Section has no content ("Returns") | -259 | @expect("D414: Section has no content ('Returns')") -260 | def valid_google_style_section(): # noqa: D406, D407 -261 | """Toggle the gizmo. - | _____^ -262 | | -263 | | Args: -264 | | note: A random string. -265 | | -266 | | Returns: -267 | | -268 | | Raises: -269 | | RandomError: A random error that occurs randomly. -270 | | -271 | | """ - | |_______^ D414 +264 | note: A random string. +265 | +266 | Returns: + | ^^^^^^^ D414 +267 | +268 | Raises: | -sections.py:558:5: D414 Section has no content ("Returns") +sections.py:563:9: D414 Section has no content ("Returns") | -557 | def titlecase_sub_section_header(): -558 | """Below, `Returns:` should be considered a section header. - | _____^ -559 | | -560 | | Args: -561 | | Here's a note. -562 | | -563 | | Returns: -564 | | """ - | |_______^ D414 +561 | Here's a note. +562 | +563 | Returns: + | ^^^^^^^ D414 +564 | """ | - - From 9872f51293ad760db8d512e34fdba4056d86c195 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Tue, 2 Apr 2024 20:51:59 -0700 Subject: [PATCH 35/54] Drop support for `root_uri` as an initialization parameter in `ruff_server` (#10743) ## Summary Needed for https://github.com/astral-sh/ruff/pull/10686. We no longer support `root_uri` as an initialization parameter, relying solely on `workspace_folders` to find the working directories. This means that the minimum supported LSP version is now `0.3.6`. ## Test Plan When opening a folder in VS Code, you shouldn't see any errors in the log which say `No workspace(s) were provided(...)`. --- crates/ruff_server/src/server.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index 5bce4bf79498f..b2a51a2a158b1 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -52,9 +52,8 @@ impl Server { let workspaces = init_params .workspace_folders .map(|folders| folders.into_iter().map(|folder| folder.uri).collect()) - .or_else(|| init_params.root_uri.map(|u| vec![u])) .or_else(|| { - tracing::debug!("No root URI or workspace(s) were provided during initialization. Using the current working directory as a default workspace..."); + tracing::debug!("No workspace(s) were provided during initialization. Using the current working directory as a default workspace..."); Some(vec![types::Url::from_file_path(std::env::current_dir().ok()?).ok()?]) }) .ok_or_else(|| { From 6e1c061e5f00fc48ce76946e31f6f6abd8cf761e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 06:37:03 +0000 Subject: [PATCH 36/54] chore(deps): update rust crate lsp-types to v0.95.1 (#10686) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [lsp-types](https://togithub.com/gluon-lang/lsp-types) | workspace.dependencies | patch | `0.95.0` -> `0.95.1` | --- ### Release Notes
gluon-lang/lsp-types (lsp-types) ### [`v0.95.1`](https://togithub.com/gluon-lang/lsp-types/blob/HEAD/CHANGELOG.md#v0951-2024-03-18) [Compare Source](https://togithub.com/gluon-lang/lsp-types/compare/v0.95.0...v0.95.1) ##### v0.95.1 (2024-03-18)
--- ### Configuration 📅 **Schedule**: Branch creation - "on Monday" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/astral-sh/ruff). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd72c72219b28..51e0137c31194 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1357,9 +1357,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.95.0" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984" +checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365" dependencies = [ "bitflags 1.3.2", "serde", From d467aa78c228d7447187486167feeb6fa3faaf99 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 3 Apr 2024 16:57:19 +0800 Subject: [PATCH 37/54] Remove an unused dependency (#10747) ## Summary Continuation of #10475, I improved [`cargo shear`](https://github.com/Boshen/cargo-shear) even more. We can put this in CI once I test it a bit more, given that [ignoring false positives](https://github.com/Boshen/cargo-shear?tab=readme-ov-file#ignore-false-positives) has been implemented. ## Test Plan `cargo check --all-features --all-targets` --- Cargo.lock | 1 - crates/ruff_python_parser/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51e0137c31194..d3a0680b71b3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2337,7 +2337,6 @@ name = "ruff_python_parser" version = "0.0.0" dependencies = [ "anyhow", - "bitflags 2.5.0", "bstr", "insta", "is-macro", diff --git a/crates/ruff_python_parser/Cargo.toml b/crates/ruff_python_parser/Cargo.toml index 2ccf94a8b181f..425537b75e14a 100644 --- a/crates/ruff_python_parser/Cargo.toml +++ b/crates/ruff_python_parser/Cargo.toml @@ -18,7 +18,6 @@ ruff_python_ast = { path = "../ruff_python_ast" } ruff_text_size = { path = "../ruff_text_size" } anyhow = { workspace = true } -bitflags = { workspace = true } bstr = { workspace = true } is-macro = { workspace = true } itertools = { workspace = true } From 257964a8bcb0bc3baae73e02292cc70810a35485 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Wed, 3 Apr 2024 09:22:17 -0700 Subject: [PATCH 38/54] `ruff server` now supports `source.fixAll` source action (#10597) ## Summary `ruff server` now has source action `source.fixAll` as an available code action. This also fixes https://github.com/astral-sh/ruff/issues/10593 in the process of revising the code for quick fix code actions. ## Test Plan https://github.com/astral-sh/ruff/assets/19577865/f4c07425-e68a-445f-a4ed-949c9197a6be --- crates/ruff_server/src/edit.rs | 2 + crates/ruff_server/src/edit/replacement.rs | 98 ++++++++++ crates/ruff_server/src/fix.rs | 79 ++++++++ crates/ruff_server/src/lib.rs | 6 + crates/ruff_server/src/lint.rs | 69 ++++++- crates/ruff_server/src/server.rs | 68 ++++++- crates/ruff_server/src/server/api.rs | 13 +- crates/ruff_server/src/server/api/requests.rs | 4 +- .../src/server/api/requests/code_action.rs | 176 +++++++++++------- .../api/requests/code_action_resolve.rs | 82 ++++++++ .../src/server/api/requests/format.rs | 108 +---------- crates/ruff_server/src/server/api/traits.rs | 4 +- crates/ruff_server/src/session.rs | 22 ++- crates/ruff_server/src/session/settings.rs | 25 +++ crates/ruff_server/src/session/types.rs | 3 - 15 files changed, 566 insertions(+), 193 deletions(-) create mode 100644 crates/ruff_server/src/edit/replacement.rs create mode 100644 crates/ruff_server/src/fix.rs create mode 100644 crates/ruff_server/src/server/api/requests/code_action_resolve.rs create mode 100644 crates/ruff_server/src/session/settings.rs delete mode 100644 crates/ruff_server/src/session/types.rs diff --git a/crates/ruff_server/src/edit.rs b/crates/ruff_server/src/edit.rs index 0de59793397fe..9af598eaebcac 100644 --- a/crates/ruff_server/src/edit.rs +++ b/crates/ruff_server/src/edit.rs @@ -2,11 +2,13 @@ mod document; mod range; +mod replacement; pub use document::Document; pub(crate) use document::DocumentVersion; use lsp_types::PositionEncodingKind; pub(crate) use range::{RangeExt, ToRangeExt}; +pub(crate) use replacement::Replacement; /// A convenient enumeration for supported text encodings. Can be converted to [`lsp_types::PositionEncodingKind`]. // Please maintain the order from least to greatest priority for the derived `Ord` impl. diff --git a/crates/ruff_server/src/edit/replacement.rs b/crates/ruff_server/src/edit/replacement.rs new file mode 100644 index 0000000000000..24a58ec3f15d6 --- /dev/null +++ b/crates/ruff_server/src/edit/replacement.rs @@ -0,0 +1,98 @@ +use ruff_text_size::{TextLen, TextRange, TextSize}; + +pub(crate) struct Replacement { + pub(crate) source_range: TextRange, + pub(crate) modified_range: TextRange, +} + +impl Replacement { + /// Creates a [`Replacement`] that describes the `source_range` of `source` to replace + /// with `modified` sliced by `modified_range`. + pub(crate) fn between( + source: &str, + source_line_starts: &[TextSize], + modified: &str, + modified_line_starts: &[TextSize], + ) -> Self { + let mut source_start = TextSize::default(); + let mut replaced_start = TextSize::default(); + let mut source_end = source.text_len(); + let mut replaced_end = modified.text_len(); + let mut line_iter = source_line_starts + .iter() + .copied() + .zip(modified_line_starts.iter().copied()); + for (source_line_start, modified_line_start) in line_iter.by_ref() { + if source_line_start != modified_line_start + || source[TextRange::new(source_start, source_line_start)] + != modified[TextRange::new(replaced_start, modified_line_start)] + { + break; + } + source_start = source_line_start; + replaced_start = modified_line_start; + } + + let mut line_iter = line_iter.rev(); + + for (old_line_start, new_line_start) in line_iter.by_ref() { + if old_line_start <= source_start + || new_line_start <= replaced_start + || source[TextRange::new(old_line_start, source_end)] + != modified[TextRange::new(new_line_start, replaced_end)] + { + break; + } + source_end = old_line_start; + replaced_end = new_line_start; + } + + Replacement { + source_range: TextRange::new(source_start, source_end), + modified_range: TextRange::new(replaced_start, replaced_end), + } + } +} + +#[cfg(test)] +mod tests { + use ruff_source_file::LineIndex; + + use super::Replacement; + + #[test] + fn find_replacement_range_works() { + let original = r#" + aaaa + bbbb + cccc + dddd + eeee + "#; + let original_index = LineIndex::from_source_text(original); + let new = r#" + bb + cccc + dd + "#; + let new_index = LineIndex::from_source_text(new); + let expected = r#" + bb + cccc + dd + "#; + let replacement = Replacement::between( + original, + original_index.line_starts(), + new, + new_index.line_starts(), + ); + let mut test = original.to_string(); + test.replace_range( + replacement.source_range.start().to_usize()..replacement.source_range.end().to_usize(), + &new[replacement.modified_range], + ); + + assert_eq!(expected, &test); + } +} diff --git a/crates/ruff_server/src/fix.rs b/crates/ruff_server/src/fix.rs new file mode 100644 index 0000000000000..fa5607f972228 --- /dev/null +++ b/crates/ruff_server/src/fix.rs @@ -0,0 +1,79 @@ +use ruff_linter::{ + linter::{FixerResult, LinterResult}, + settings::{flags, types::UnsafeFixes, LinterSettings}, + source_kind::SourceKind, +}; +use ruff_python_ast::PySourceType; +use ruff_source_file::LineIndex; +use std::{borrow::Cow, path::Path}; + +use crate::{ + edit::{Replacement, ToRangeExt}, + PositionEncoding, +}; + +pub(crate) fn fix_all( + document: &crate::edit::Document, + linter_settings: &LinterSettings, + encoding: PositionEncoding, +) -> crate::Result> { + let source = document.contents(); + + let source_type = PySourceType::default(); + + // TODO(jane): Support Jupyter Notebooks + let source_kind = SourceKind::Python(source.to_string()); + + // We need to iteratively apply all safe fixes onto a single file and then + // create a diff between the modified file and the original source to use as a single workspace + // edit. + // If we simply generated the diagnostics with `check_path` and then applied fixes individually, + // there's a possibility they could overlap or introduce new problems that need to be fixed, + // which is inconsistent with how `ruff check --fix` works. + let FixerResult { + transformed, + result: LinterResult { error, .. }, + .. + } = ruff_linter::linter::lint_fix( + Path::new(""), + None, + flags::Noqa::Enabled, + UnsafeFixes::Disabled, + linter_settings, + &source_kind, + source_type, + )?; + + if let Some(error) = error { + // abort early if a parsing error occurred + return Err(anyhow::anyhow!( + "A parsing error occurred during `fix_all`: {error}" + )); + } + + // fast path: if `transformed` is still borrowed, no changes were made and we can return early + if let Cow::Borrowed(_) = transformed { + return Ok(vec![]); + } + + let modified = transformed.source_code(); + + let modified_index = LineIndex::from_source_text(modified); + + let source_index = document.index(); + + let Replacement { + source_range, + modified_range, + } = Replacement::between( + source, + source_index.line_starts(), + modified, + modified_index.line_starts(), + ); + + Ok(vec![lsp_types::TextEdit { + range: source_range.to_range(source, source_index, encoding), + new_text: modified[modified_range].to_owned(), + }]) +} diff --git a/crates/ruff_server/src/lib.rs b/crates/ruff_server/src/lib.rs index b4d50d7523344..4814436678f6f 100644 --- a/crates/ruff_server/src/lib.rs +++ b/crates/ruff_server/src/lib.rs @@ -1,9 +1,11 @@ //! ## The Ruff Language Server pub use edit::{Document, PositionEncoding}; +use lsp_types::CodeActionKind; pub use server::Server; mod edit; +mod fix; mod format; mod lint; mod server; @@ -12,6 +14,10 @@ mod session; pub(crate) const SERVER_NAME: &str = "ruff"; pub(crate) const DIAGNOSTIC_NAME: &str = "Ruff"; +pub(crate) const SOURCE_FIX_ALL_RUFF: CodeActionKind = CodeActionKind::new("source.fixAll.ruff"); +pub(crate) const SOURCE_ORGANIZE_IMPORTS_RUFF: CodeActionKind = + CodeActionKind::new("source.organizeImports.ruff"); + /// A common result type used in most cases where a /// result type is needed. pub(crate) type Result = anyhow::Result; diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index 347e836c0b8db..ba8bef1e26825 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -16,14 +16,26 @@ use ruff_python_index::Indexer; use ruff_python_parser::lexer::LexResult; use ruff_python_parser::AsMode; use ruff_source_file::Locator; +use ruff_text_size::Ranged; use serde::{Deserialize, Serialize}; use crate::{edit::ToRangeExt, PositionEncoding, DIAGNOSTIC_NAME}; -#[derive(Serialize, Deserialize)] -pub(crate) struct DiagnosticFix { +/// This is serialized on the diagnostic `data` field. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct AssociatedDiagnosticData { pub(crate) kind: DiagnosticKind, pub(crate) fix: Fix, + pub(crate) code: String, +} + +/// Describes a fix for `fixed_diagnostic` that applies `document_edits` to the source. +#[derive(Clone, Debug)] +pub(crate) struct DiagnosticFix { + pub(crate) fixed_diagnostic: lsp_types::Diagnostic, + pub(crate) title: String, + pub(crate) code: String, + pub(crate) document_edits: Vec, } pub(crate) fn check( @@ -78,6 +90,56 @@ pub(crate) fn check( .collect() } +pub(crate) fn fixes_for_diagnostics<'d>( + document: &'d crate::edit::Document, + url: &'d lsp_types::Url, + encoding: PositionEncoding, + version: crate::edit::DocumentVersion, + diagnostics: Vec, +) -> crate::Result> { + diagnostics + .into_iter() + .map(move |mut diagnostic| { + let Some(data) = diagnostic.data.take() else { + return Ok(None); + }; + let fixed_diagnostic = diagnostic; + let associated_data: crate::lint::AssociatedDiagnosticData = + serde_json::from_value(data).map_err(|err| { + anyhow::anyhow!("failed to deserialize diagnostic data: {err}") + })?; + let edits = associated_data + .fix + .edits() + .iter() + .map(|edit| lsp_types::TextEdit { + range: edit + .range() + .to_range(document.contents(), document.index(), encoding), + new_text: edit.content().unwrap_or_default().to_string(), + }); + + let document_edits = vec![lsp_types::TextDocumentEdit { + text_document: lsp_types::OptionalVersionedTextDocumentIdentifier::new( + url.clone(), + version, + ), + edits: edits.map(lsp_types::OneOf::Left).collect(), + }]; + Ok(Some(DiagnosticFix { + fixed_diagnostic, + code: associated_data.code, + title: associated_data + .kind + .suggestion + .unwrap_or(associated_data.kind.name), + document_edits, + })) + }) + .filter_map(crate::Result::transpose) + .collect() +} + fn to_lsp_diagnostic( diagnostic: Diagnostic, document: &crate::edit::Document, @@ -92,9 +154,10 @@ fn to_lsp_diagnostic( let data = fix.and_then(|fix| { fix.applies(Applicability::Unsafe) .then(|| { - serde_json::to_value(&DiagnosticFix { + serde_json::to_value(&AssociatedDiagnosticData { kind: kind.clone(), fix, + code: rule.noqa_code().to_string(), }) .ok() }) diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index b2a51a2a158b1..53bafa06a49ae 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -72,10 +72,10 @@ impl Server { Ok(Self { conn, - client_capabilities, threads, worker_threads, - session: Session::new(&server_capabilities, &workspaces)?, + session: Session::new(&client_capabilities, &server_capabilities, &workspaces)?, + client_capabilities, }) } @@ -192,14 +192,15 @@ impl Server { position_encoding: Some(position_encoding.into()), code_action_provider: Some(types::CodeActionProviderCapability::Options( CodeActionOptions { - code_action_kinds: Some(vec![ - CodeActionKind::QUICKFIX, - CodeActionKind::SOURCE_ORGANIZE_IMPORTS, - ]), + code_action_kinds: Some( + SupportedCodeAction::all() + .flat_map(|action| action.kinds().into_iter()) + .collect(), + ), work_done_progress_options: WorkDoneProgressOptions { work_done_progress: Some(true), }, - resolve_provider: Some(false), + resolve_provider: Some(true), }, )), workspace: Some(types::WorkspaceServerCapabilities { @@ -235,3 +236,56 @@ impl Server { } } } + +/// The code actions we support. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) enum SupportedCodeAction { + /// Maps to the `quickfix` code action kind. Quick fix code actions are shown under + /// their respective diagnostics. Quick fixes are only created where the fix applicability is + /// at least [`ruff_diagnostics::Applicability::Unsafe`]. + QuickFix, + /// Maps to the `source.fixAll` and `source.fixAll.ruff` code action kinds. + /// This is a source action that applies all safe fixes to the currently open document. + SourceFixAll, + /// Maps to `source.organizeImports` and `source.organizeImports.ruff` code action kinds. + /// This is a source action that applies import sorting fixes to the currently open document. + #[allow(dead_code)] // TODO: remove + SourceOrganizeImports, +} + +impl SupportedCodeAction { + /// Returns the possible LSP code action kind(s) that map to this code action. + fn kinds(self) -> Vec { + match self { + Self::QuickFix => vec![CodeActionKind::QUICKFIX], + Self::SourceFixAll => vec![CodeActionKind::SOURCE_FIX_ALL, crate::SOURCE_FIX_ALL_RUFF], + Self::SourceOrganizeImports => vec![ + CodeActionKind::SOURCE_ORGANIZE_IMPORTS, + crate::SOURCE_ORGANIZE_IMPORTS_RUFF, + ], + } + } + + /// Returns all code actions kinds that the server currently supports. + fn all() -> impl Iterator { + [ + Self::QuickFix, + Self::SourceFixAll, + // Self::SourceOrganizeImports, + ] + .into_iter() + } +} + +impl TryFrom for SupportedCodeAction { + type Error = (); + + fn try_from(kind: CodeActionKind) -> std::result::Result { + for supported_kind in Self::all() { + if supported_kind.kinds().contains(&kind) { + return Ok(supported_kind); + } + } + Err(()) + } +} diff --git a/crates/ruff_server/src/server/api.rs b/crates/ruff_server/src/server/api.rs index 21f9a26a42f82..90cf543a29fac 100644 --- a/crates/ruff_server/src/server/api.rs +++ b/crates/ruff_server/src/server/api.rs @@ -16,8 +16,8 @@ use super::{client::Responder, schedule::BackgroundSchedule, Result}; /// given the parameter type used by the implementer. macro_rules! define_document_url { ($params:ident: &$p:ty) => { - fn document_url($params: &$p) -> &lsp_types::Url { - &$params.text_document.uri + fn document_url($params: &$p) -> std::borrow::Cow { + std::borrow::Cow::Borrowed(&$params.text_document.uri) } }; } @@ -28,10 +28,13 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> { let id = req.id.clone(); match req.method.as_str() { - request::CodeAction::METHOD => background_request_task::( + request::CodeActions::METHOD => background_request_task::( req, BackgroundSchedule::LatencySensitive, ), + request::CodeActionResolve::METHOD => { + background_request_task::(req, BackgroundSchedule::Worker) + } request::DocumentDiagnostic::METHOD => { background_request_task::( req, @@ -102,7 +105,7 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>( let (id, params) = cast_request::(req)?; Ok(Task::background(schedule, move |session: &Session| { // TODO(jane): we should log an error if we can't take a snapshot. - let Some(snapshot) = session.take_snapshot(R::document_url(¶ms)) else { + let Some(snapshot) = session.take_snapshot(&R::document_url(¶ms)) else { return Box::new(|_, _| {}); }; Box::new(move |notifier, responder| { @@ -131,7 +134,7 @@ fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationH let (id, params) = cast_notification::(req)?; Ok(Task::background(schedule, move |session: &Session| { // TODO(jane): we should log an error if we can't take a snapshot. - let Some(snapshot) = session.take_snapshot(N::document_url(¶ms)) else { + let Some(snapshot) = session.take_snapshot(&N::document_url(¶ms)) else { return Box::new(|_, _| {}); }; Box::new(move |notifier, _| { diff --git a/crates/ruff_server/src/server/api/requests.rs b/crates/ruff_server/src/server/api/requests.rs index d29a60a660d49..3883c009bdec4 100644 --- a/crates/ruff_server/src/server/api/requests.rs +++ b/crates/ruff_server/src/server/api/requests.rs @@ -1,4 +1,5 @@ mod code_action; +mod code_action_resolve; mod diagnostic; mod format; mod format_range; @@ -7,7 +8,8 @@ use super::{ define_document_url, traits::{BackgroundDocumentRequestHandler, RequestHandler}, }; -pub(super) use code_action::CodeAction; +pub(super) use code_action::CodeActions; +pub(super) use code_action_resolve::CodeActionResolve; pub(super) use diagnostic::DocumentDiagnostic; pub(super) use format::Format; pub(super) use format_range::FormatRange; diff --git a/crates/ruff_server/src/server/api/requests/code_action.rs b/crates/ruff_server/src/server/api/requests/code_action.rs index 235b651078f06..a16eab75af9c3 100644 --- a/crates/ruff_server/src/server/api/requests/code_action.rs +++ b/crates/ruff_server/src/server/api/requests/code_action.rs @@ -1,81 +1,131 @@ -use crate::edit::ToRangeExt; +use crate::lint::fixes_for_diagnostics; use crate::server::api::LSPResult; +use crate::server::SupportedCodeAction; use crate::server::{client::Notifier, Result}; use crate::session::DocumentSnapshot; +use crate::DIAGNOSTIC_NAME; +use lsp_server::ErrorCode; use lsp_types::{self as types, request as req}; -use ruff_text_size::Ranged; +use rustc_hash::FxHashSet; +use types::{CodeActionKind, CodeActionOrCommand}; -pub(crate) struct CodeAction; +use super::code_action_resolve::resolve_edit_for_fix_all; -impl super::RequestHandler for CodeAction { +pub(crate) struct CodeActions; + +impl super::RequestHandler for CodeActions { type RequestType = req::CodeActionRequest; } -impl super::BackgroundDocumentRequestHandler for CodeAction { +impl super::BackgroundDocumentRequestHandler for CodeActions { super::define_document_url!(params: &types::CodeActionParams); fn run_with_snapshot( snapshot: DocumentSnapshot, _notifier: Notifier, params: types::CodeActionParams, ) -> Result> { - let document = snapshot.document(); - let url = snapshot.url(); - let encoding = snapshot.encoding(); - let version = document.version(); - let actions: Result> = params - .context - .diagnostics - .into_iter() - .map(|diagnostic| { - let Some(data) = diagnostic.data else { - return Ok(None); - }; - let diagnostic_fix: crate::lint::DiagnosticFix = serde_json::from_value(data) - .map_err(|err| anyhow::anyhow!("failed to deserialize diagnostic data: {err}")) - .with_failure_code(lsp_server::ErrorCode::ParseError)?; - let edits = diagnostic_fix - .fix - .edits() + let mut response: types::CodeActionResponse = types::CodeActionResponse::default(); + + let supported_code_actions = supported_code_actions(params.context.only); + + if supported_code_actions.contains(&SupportedCodeAction::QuickFix) { + response.extend( + quick_fix(&snapshot, params.context.diagnostics) + .with_failure_code(ErrorCode::InternalError)?, + ); + } + + if supported_code_actions.contains(&SupportedCodeAction::SourceFixAll) { + response.push(fix_all(&snapshot).with_failure_code(ErrorCode::InternalError)?); + } + + if supported_code_actions.contains(&SupportedCodeAction::SourceOrganizeImports) { + todo!("Implement the `source.organizeImports` code action"); + } + + Ok(Some(response)) + } +} + +fn quick_fix( + snapshot: &DocumentSnapshot, + diagnostics: Vec, +) -> crate::Result + '_> { + let document = snapshot.document(); + + let fixes = fixes_for_diagnostics( + document, + snapshot.url(), + snapshot.encoding(), + document.version(), + diagnostics, + )?; + + Ok(fixes.into_iter().map(|fix| { + types::CodeActionOrCommand::CodeAction(types::CodeAction { + title: format!("{DIAGNOSTIC_NAME} ({}): {}", fix.code, fix.title), + kind: Some(types::CodeActionKind::QUICKFIX), + edit: Some(types::WorkspaceEdit { + document_changes: Some(types::DocumentChanges::Edits(fix.document_edits.clone())), + ..Default::default() + }), + diagnostics: Some(vec![fix.fixed_diagnostic.clone()]), + data: Some(serde_json::to_value(snapshot.url()).expect("document url to serialize")), + ..Default::default() + }) + })) +} + +fn fix_all(snapshot: &DocumentSnapshot) -> crate::Result { + let document = snapshot.document(); + + let (edit, data) = if snapshot + .resolved_client_capabilities() + .code_action_deferred_edit_resolution + { + // The editor will request the edit in a `CodeActionsResolve` request + ( + None, + Some(serde_json::to_value(snapshot.url()).expect("document url to serialize")), + ) + } else { + ( + Some(resolve_edit_for_fix_all( + document, + snapshot.url(), + &snapshot.configuration().linter, + snapshot.encoding(), + )?), + None, + ) + }; + let action = types::CodeAction { + title: format!("{DIAGNOSTIC_NAME}: Fix all auto-fixable problems"), + kind: Some(types::CodeActionKind::SOURCE_FIX_ALL), + edit, + data, + ..Default::default() + }; + Ok(types::CodeActionOrCommand::CodeAction(action)) +} + +/// If `action_filter` is `None`, this returns [`SupportedCodeActionKind::all()`]. Otherwise, +/// the list is filtered. +fn supported_code_actions( + action_filter: Option>, +) -> FxHashSet { + let Some(action_filter) = action_filter else { + return SupportedCodeAction::all().collect(); + }; + + SupportedCodeAction::all() + .filter(move |action| { + action_filter.iter().any(|filter| { + action + .kinds() .iter() - .map(|edit| types::TextEdit { - range: edit.range().to_range( - document.contents(), - document.index(), - encoding, - ), - new_text: edit.content().unwrap_or_default().to_string(), - }); - - let changes = vec![types::TextDocumentEdit { - text_document: types::OptionalVersionedTextDocumentIdentifier::new( - url.clone(), - version, - ), - edits: edits.map(types::OneOf::Left).collect(), - }]; - - let title = diagnostic_fix - .kind - .suggestion - .unwrap_or(diagnostic_fix.kind.name); - Ok(Some(types::CodeAction { - title, - kind: Some(types::CodeActionKind::QUICKFIX), - edit: Some(types::WorkspaceEdit { - document_changes: Some(types::DocumentChanges::Edits(changes)), - ..Default::default() - }), - ..Default::default() - })) + .any(|kind| kind.as_str().starts_with(filter.as_str())) }) - .collect(); - - Ok(Some( - actions? - .into_iter() - .flatten() - .map(types::CodeActionOrCommand::CodeAction) - .collect(), - )) - } + }) + .collect() } diff --git a/crates/ruff_server/src/server/api/requests/code_action_resolve.rs b/crates/ruff_server/src/server/api/requests/code_action_resolve.rs new file mode 100644 index 0000000000000..ffb05c621062f --- /dev/null +++ b/crates/ruff_server/src/server/api/requests/code_action_resolve.rs @@ -0,0 +1,82 @@ +use std::borrow::Cow; + +use crate::server::api::LSPResult; +use crate::server::SupportedCodeAction; +use crate::server::{client::Notifier, Result}; +use crate::session::DocumentSnapshot; +use crate::PositionEncoding; +use lsp_server::ErrorCode; +use lsp_types::{self as types, request as req}; +use ruff_linter::settings::LinterSettings; + +pub(crate) struct CodeActionResolve; + +impl super::RequestHandler for CodeActionResolve { + type RequestType = req::CodeActionResolveRequest; +} + +impl super::BackgroundDocumentRequestHandler for CodeActionResolve { + fn document_url(params: &types::CodeAction) -> Cow { + let uri: lsp_types::Url = serde_json::from_value(params.data.clone().unwrap_or_default()) + .expect("code actions should have a URI in their data fields"); + std::borrow::Cow::Owned(uri) + } + fn run_with_snapshot( + snapshot: DocumentSnapshot, + _notifier: Notifier, + mut action: types::CodeAction, + ) -> Result { + let document = snapshot.document(); + + let action_kind: SupportedCodeAction = action + .kind + .clone() + .ok_or(anyhow::anyhow!("No kind was given for code action")) + .with_failure_code(ErrorCode::InvalidParams)? + .try_into() + .map_err(|()| anyhow::anyhow!("Code action was of an invalid kind")) + .with_failure_code(ErrorCode::InvalidParams)?; + + action.edit = match action_kind { + SupportedCodeAction::SourceFixAll => Some( + resolve_edit_for_fix_all( + document, + snapshot.url(), + &snapshot.configuration().linter, + snapshot.encoding(), + ) + .with_failure_code(ErrorCode::InternalError)?, + ), + SupportedCodeAction::SourceOrganizeImports => { + todo!("Support `source.organizeImports`") + } + SupportedCodeAction::QuickFix => { + return Err(anyhow::anyhow!( + "Got a code action that should not need additional resolution: {action_kind:?}" + )) + .with_failure_code(ErrorCode::InvalidParams) + } + }; + + Ok(action) + } +} + +pub(super) fn resolve_edit_for_fix_all( + document: &crate::edit::Document, + url: &types::Url, + linter_settings: &LinterSettings, + encoding: PositionEncoding, +) -> crate::Result { + Ok(types::WorkspaceEdit { + changes: Some( + [( + url.clone(), + crate::fix::fix_all(document, linter_settings, encoding)?, + )] + .into_iter() + .collect(), + ), + ..Default::default() + }) +} diff --git a/crates/ruff_server/src/server/api/requests/format.rs b/crates/ruff_server/src/server/api/requests/format.rs index 384539ad092fa..566e3609054bf 100644 --- a/crates/ruff_server/src/server/api/requests/format.rs +++ b/crates/ruff_server/src/server/api/requests/format.rs @@ -1,10 +1,9 @@ -use crate::edit::ToRangeExt; +use crate::edit::{Replacement, ToRangeExt}; use crate::server::api::LSPResult; use crate::server::{client::Notifier, Result}; use crate::session::DocumentSnapshot; use lsp_types::{self as types, request as req}; use ruff_source_file::LineIndex; -use ruff_text_size::{TextLen, TextRange, TextSize}; use types::TextEdit; pub(crate) struct Format; @@ -33,8 +32,8 @@ impl super::BackgroundDocumentRequestHandler for Format { let unformatted_index = doc.index(); let Replacement { - source_range: replace_range, - formatted_range: replacement_text_range, + source_range, + modified_range: formatted_range, } = Replacement::between( source, unformatted_index.line_starts(), @@ -43,105 +42,8 @@ impl super::BackgroundDocumentRequestHandler for Format { ); Ok(Some(vec![TextEdit { - range: replace_range.to_range(source, unformatted_index, snapshot.encoding()), - new_text: formatted[replacement_text_range].to_owned(), + range: source_range.to_range(source, unformatted_index, snapshot.encoding()), + new_text: formatted[formatted_range].to_owned(), }])) } } - -struct Replacement { - source_range: TextRange, - formatted_range: TextRange, -} - -impl Replacement { - /// Creates a [`Replacement`] that describes the `replace_range` of `old_text` to replace - /// with `new_text` sliced by `replacement_text_range`. - fn between( - source: &str, - source_line_starts: &[TextSize], - formatted: &str, - formatted_line_starts: &[TextSize], - ) -> Self { - let mut source_start = TextSize::default(); - let mut formatted_start = TextSize::default(); - let mut source_end = source.text_len(); - let mut formatted_end = formatted.text_len(); - let mut line_iter = source_line_starts - .iter() - .copied() - .zip(formatted_line_starts.iter().copied()); - for (source_line_start, formatted_line_start) in line_iter.by_ref() { - if source_line_start != formatted_line_start - || source[TextRange::new(source_start, source_line_start)] - != formatted[TextRange::new(formatted_start, formatted_line_start)] - { - break; - } - source_start = source_line_start; - formatted_start = formatted_line_start; - } - - let mut line_iter = line_iter.rev(); - - for (old_line_start, new_line_start) in line_iter.by_ref() { - if old_line_start <= source_start - || new_line_start <= formatted_start - || source[TextRange::new(old_line_start, source_end)] - != formatted[TextRange::new(new_line_start, formatted_end)] - { - break; - } - source_end = old_line_start; - formatted_end = new_line_start; - } - - Replacement { - source_range: TextRange::new(source_start, source_end), - formatted_range: TextRange::new(formatted_start, formatted_end), - } - } -} - -#[cfg(test)] -mod tests { - use ruff_source_file::LineIndex; - - use crate::server::api::requests::format::Replacement; - - #[test] - fn find_replacement_range_works() { - let original = r#" - aaaa - bbbb - cccc - dddd - eeee - "#; - let original_index = LineIndex::from_source_text(original); - let new = r#" - bb - cccc - dd - "#; - let new_index = LineIndex::from_source_text(new); - let expected = r#" - bb - cccc - dd - "#; - let replacement = Replacement::between( - original, - original_index.line_starts(), - new, - new_index.line_starts(), - ); - let mut test = original.to_string(); - test.replace_range( - replacement.source_range.start().to_usize()..replacement.source_range.end().to_usize(), - &new[replacement.formatted_range], - ); - - assert_eq!(expected, &test); - } -} diff --git a/crates/ruff_server/src/server/api/traits.rs b/crates/ruff_server/src/server/api/traits.rs index 54639546dc9fc..7a980ebfca8e7 100644 --- a/crates/ruff_server/src/server/api/traits.rs +++ b/crates/ruff_server/src/server/api/traits.rs @@ -31,7 +31,7 @@ pub(super) trait BackgroundDocumentRequestHandler: RequestHandler { /// implementation. fn document_url( params: &<::RequestType as Request>::Params, - ) -> &lsp_types::Url; + ) -> std::borrow::Cow; fn run_with_snapshot( snapshot: DocumentSnapshot, @@ -66,7 +66,7 @@ pub(super) trait BackgroundDocumentNotificationHandler: NotificationHandler { /// implementation. fn document_url( params: &<::NotificationType as LSPNotification>::Params, - ) -> &lsp_types::Url; + ) -> std::borrow::Cow; fn run_with_snapshot( snapshot: DocumentSnapshot, diff --git a/crates/ruff_server/src/session.rs b/crates/ruff_server/src/session.rs index aa039b28363d4..22f1ed50ce771 100644 --- a/crates/ruff_server/src/session.rs +++ b/crates/ruff_server/src/session.rs @@ -1,34 +1,36 @@ //! Data model, state management, and configuration resolution. -mod types; +mod settings; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::{ops::Deref, sync::Arc}; use anyhow::anyhow; -use lsp_types::{ServerCapabilities, Url}; +use lsp_types::{ClientCapabilities, ServerCapabilities, Url}; use ruff_workspace::resolver::{ConfigurationTransformer, Relativity}; use rustc_hash::FxHashMap; use crate::edit::{Document, DocumentVersion}; use crate::PositionEncoding; +use self::settings::ResolvedClientCapabilities; + /// The global state for the LSP pub(crate) struct Session { /// Workspace folders in the current session, which contain the state of all open files. workspaces: Workspaces, /// The global position encoding, negotiated during LSP initialization. position_encoding: PositionEncoding, - /// Extension-specific settings, set by the client, that apply to all workspace folders. - #[allow(dead_code)] - lsp_settings: types::ExtensionSettings, + /// Tracks what LSP features the client supports and doesn't support. + resolved_client_capabilities: Arc, } /// An immutable snapshot of `Session` that references /// a specific document. pub(crate) struct DocumentSnapshot { configuration: Arc, + resolved_client_capabilities: Arc, document_ref: DocumentRef, position_encoding: PositionEncoding, url: Url, @@ -70,6 +72,7 @@ pub(crate) struct DocumentRef { impl Session { pub(crate) fn new( + client_capabilities: &ClientCapabilities, server_capabilities: &ServerCapabilities, workspaces: &[Url], ) -> crate::Result { @@ -79,7 +82,9 @@ impl Session { .as_ref() .and_then(|encoding| encoding.try_into().ok()) .unwrap_or_default(), - lsp_settings: types::ExtensionSettings, + resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new( + client_capabilities, + )), workspaces: Workspaces::new(workspaces)?, }) } @@ -87,6 +92,7 @@ impl Session { pub(crate) fn take_snapshot(&self, url: &Url) -> Option { Some(DocumentSnapshot { configuration: self.workspaces.configuration(url)?.clone(), + resolved_client_capabilities: self.resolved_client_capabilities.clone(), document_ref: self.workspaces.snapshot(url)?, position_encoding: self.position_encoding, url: url.clone(), @@ -196,6 +202,10 @@ impl DocumentSnapshot { &self.configuration } + pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities { + &self.resolved_client_capabilities + } + pub(crate) fn document(&self) -> &DocumentRef { &self.document_ref } diff --git a/crates/ruff_server/src/session/settings.rs b/crates/ruff_server/src/session/settings.rs new file mode 100644 index 0000000000000..f415aa7541169 --- /dev/null +++ b/crates/ruff_server/src/session/settings.rs @@ -0,0 +1,25 @@ +use lsp_types::ClientCapabilities; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub(crate) struct ResolvedClientCapabilities { + pub(crate) code_action_deferred_edit_resolution: bool, +} + +impl ResolvedClientCapabilities { + pub(super) fn new(client_capabilities: &ClientCapabilities) -> Self { + let code_action_settings = client_capabilities + .text_document + .as_ref() + .and_then(|doc_settings| doc_settings.code_action.as_ref()); + let code_action_data_support = code_action_settings + .and_then(|code_action_settings| code_action_settings.data_support) + .unwrap_or_default(); + let code_action_edit_resolution = code_action_settings + .and_then(|code_action_settings| code_action_settings.resolve_support.as_ref()) + .is_some_and(|resolve_support| resolve_support.properties.contains(&"edit".into())); + Self { + code_action_deferred_edit_resolution: code_action_data_support + && code_action_edit_resolution, + } + } +} diff --git a/crates/ruff_server/src/session/types.rs b/crates/ruff_server/src/session/types.rs deleted file mode 100644 index 1ed23ae69da38..0000000000000 --- a/crates/ruff_server/src/session/types.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[allow(dead_code)] // TODO(jane): get this wired up after the pre-release -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub(crate) struct ExtensionSettings; From e0a8fb607a95614bb2e1806d9393f04688d4da00 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 3 Apr 2024 14:13:10 -0600 Subject: [PATCH 39/54] fix obsolete name in resolve_qualified_name docs (#10762) `resolve_call_path` was renamed to `resolve_qualified_name` in a6d892b1f4b45bfed918f3240218d5fcd39d3099, but the doc block for the function wasn't updated to match. --- crates/ruff_python_semantic/src/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 7745426ec9ebc..07dce98d1e919 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -680,7 +680,7 @@ impl<'a> SemanticModel<'a> { /// print(python_version) /// ``` /// - /// ...then `resolve_call_path(${python_version})` will resolve to `sys.version_info`. + /// ...then `resolve_qualified_name(${python_version})` will resolve to `sys.version_info`. pub fn resolve_qualified_name<'name, 'expr: 'name>( &self, value: &'expr Expr, From 5e2482824c029473f14f83677f0ab06471a5bc33 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 3 Apr 2024 14:44:33 -0600 Subject: [PATCH 40/54] [flake8_comprehensions] add sum/min/max to unnecessary comprehension check (C419) (#10759) Fixes #3259 ## Summary Renames `UnnecessaryComprehensionAnyAll` to `UnnecessaryComprehensionInCall` and extends the check to `sum`, `min`, and `max`, in addition to `any` and `all`. ## Test Plan Updated snapshot test. Built docs locally and verified the docs for this rule still render correctly. --- .../fixtures/flake8_comprehensions/C419.py | 4 + .../fixtures/flake8_comprehensions/C419_1.py | 8 ++ .../fixtures/flake8_comprehensions/C419_2.py | 3 + .../src/checkers/ast/analyze/expression.rs | 4 +- crates/ruff_linter/src/codes.rs | 2 +- .../src/rules/flake8_comprehensions/fixes.rs | 2 +- .../src/rules/flake8_comprehensions/mod.rs | 22 ++++- .../rules/flake8_comprehensions/rules/mod.rs | 4 +- ...s => unnecessary_comprehension_in_call.rs} | 46 +++++---- ...8_comprehensions__tests__C419_C419.py.snap | 96 +++++++++---------- ...comprehensions__tests__C419_C419_2.py.snap | 4 + ...sions__tests__preview__C419_C419_1.py.snap | 55 +++++++++++ 12 files changed, 178 insertions(+), 72 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_1.py create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_2.py rename crates/ruff_linter/src/rules/flake8_comprehensions/rules/{unnecessary_comprehension_any_all.rs => unnecessary_comprehension_in_call.rs} (59%) create mode 100644 crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419_2.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__preview__C419_C419_1.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419.py index 4a9671b1ee020..b0a15cf2d6aac 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419.py @@ -13,6 +13,10 @@ all(x.id for x in bar) any(x.id for x in bar) all((x.id for x in bar)) +# we don't lint on these in stable yet +sum([x.val for x in bar]) +min([x.val for x in bar]) +max([x.val for x in bar]) async def f() -> bool: diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_1.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_1.py new file mode 100644 index 0000000000000..b0a521e2363d2 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_1.py @@ -0,0 +1,8 @@ +sum([x.val for x in bar]) +min([x.val for x in bar]) +max([x.val for x in bar]) + +# Ok +sum(x.val for x in bar) +min(x.val for x in bar) +max(x.val for x in bar) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_2.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_2.py new file mode 100644 index 0000000000000..f8848634547f1 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C419_2.py @@ -0,0 +1,3 @@ +# no lint if shadowed +def all(x): pass +all([x.id for x in bar]) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 5a64941417858..b3fc11d7f7e15 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -705,8 +705,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { args, ); } - if checker.enabled(Rule::UnnecessaryComprehensionAnyAll) { - flake8_comprehensions::rules::unnecessary_comprehension_any_all( + if checker.enabled(Rule::UnnecessaryComprehensionInCall) { + flake8_comprehensions::rules::unnecessary_comprehension_in_call( checker, expr, func, args, keywords, ); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 60950ee89288c..75cb2966bc30f 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -398,7 +398,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Comprehensions, "16") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryComprehension), (Flake8Comprehensions, "17") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryMap), (Flake8Comprehensions, "18") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinDictCall), - (Flake8Comprehensions, "19") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryComprehensionAnyAll), + (Flake8Comprehensions, "19") => (RuleGroup::Stable, rules::flake8_comprehensions::rules::UnnecessaryComprehensionInCall), // flake8-debugger (Flake8Debugger, "0") => (RuleGroup::Stable, rules::flake8_debugger::rules::Debugger), diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs index 1febae119798b..295d8463b8015 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs @@ -793,7 +793,7 @@ pub(crate) fn fix_unnecessary_map( } /// (C419) Convert `[i for i in a]` into `i for i in a` -pub(crate) fn fix_unnecessary_comprehension_any_all( +pub(crate) fn fix_unnecessary_comprehension_in_call( expr: &Expr, locator: &Locator, stylist: &Stylist, diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs index 74c2a69ac323f..f1c765dff0646 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs @@ -12,13 +12,15 @@ mod tests { use crate::assert_messages; use crate::registry::Rule; + use crate::settings::types::PreviewMode; use crate::settings::LinterSettings; use crate::test::test_path; #[test_case(Rule::UnnecessaryCallAroundSorted, Path::new("C413.py"))] #[test_case(Rule::UnnecessaryCollectionCall, Path::new("C408.py"))] #[test_case(Rule::UnnecessaryComprehension, Path::new("C416.py"))] - #[test_case(Rule::UnnecessaryComprehensionAnyAll, Path::new("C419.py"))] + #[test_case(Rule::UnnecessaryComprehensionInCall, Path::new("C419.py"))] + #[test_case(Rule::UnnecessaryComprehensionInCall, Path::new("C419_2.py"))] #[test_case(Rule::UnnecessaryDoubleCastOrProcess, Path::new("C414.py"))] #[test_case(Rule::UnnecessaryGeneratorDict, Path::new("C402.py"))] #[test_case(Rule::UnnecessaryGeneratorList, Path::new("C400.py"))] @@ -43,6 +45,24 @@ mod tests { Ok(()) } + #[test_case(Rule::UnnecessaryComprehensionInCall, Path::new("C419_1.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_comprehensions").join(path).as_path(), + &LinterSettings { + preview: PreviewMode::Enabled, + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test_case(Rule::UnnecessaryCollectionCall, Path::new("C408.py"))] fn allow_dict_calls_with_keyword_arguments(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/mod.rs index 2c24ecfc2fa47..ff54ed3c38137 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/mod.rs @@ -1,7 +1,7 @@ pub(crate) use unnecessary_call_around_sorted::*; pub(crate) use unnecessary_collection_call::*; pub(crate) use unnecessary_comprehension::*; -pub(crate) use unnecessary_comprehension_any_all::*; +pub(crate) use unnecessary_comprehension_in_call::*; pub(crate) use unnecessary_double_cast_or_process::*; pub(crate) use unnecessary_generator_dict::*; pub(crate) use unnecessary_generator_list::*; @@ -21,7 +21,7 @@ mod helpers; mod unnecessary_call_around_sorted; mod unnecessary_collection_call; mod unnecessary_comprehension; -mod unnecessary_comprehension_any_all; +mod unnecessary_comprehension_in_call; mod unnecessary_double_cast_or_process; mod unnecessary_generator_dict; mod unnecessary_generator_list; diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_any_all.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs similarity index 59% rename from crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_any_all.rs rename to crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs index de538c6f8f184..901fb1e54abdf 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_any_all.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_comprehension_in_call.rs @@ -11,15 +11,18 @@ use crate::checkers::ast::Checker; use crate::rules::flake8_comprehensions::fixes; /// ## What it does -/// Checks for unnecessary list comprehensions passed to `any` and `all`. +/// Checks for unnecessary list comprehensions passed to builtin functions that take an iterable. /// /// ## Why is this bad? -/// `any` and `all` take any iterators, including generators. Converting a generator to a list -/// by way of a list comprehension is unnecessary and requires iterating all values, even if `any` -/// or `all` could short-circuit early. +/// Many builtin functions (this rule currently covers `any`, `all`, `min`, `max`, and `sum`) take +/// any iterable, including a generator. Constructing a temporary list via list comprehension is +/// unnecessary and wastes memory for large iterables. /// -/// For example, compare the performance of `all` with a list comprehension against that -/// of a generator in a case where an early short-circuit is possible (almost 40x faster): +/// `any` and `all` can also short-circuit iteration, saving a lot of time. The unnecessary +/// comprehension forces a full iteration of the input iterable, giving up the benefits of +/// short-circuiting. For example, compare the performance of `all` with a list comprehension +/// against that of a generator in a case where an early short-circuit is possible (almost 40x +/// faster): /// /// ```console /// In [1]: %timeit all([i for i in range(1000)]) @@ -29,22 +32,30 @@ use crate::rules::flake8_comprehensions::fixes; /// 212 ns ± 0.892 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each) /// ``` /// -/// This performance difference is due to short-circuiting; if the entire iterable has to be -/// traversed, the comprehension version may even be a bit faster (list allocation overhead is not -/// necessarily greater than generator overhead). +/// This performance improvement is due to short-circuiting. If the entire iterable has to be +/// traversed, the comprehension version may even be a bit faster: list allocation overhead is not +/// necessarily greater than generator overhead. /// -/// The generator version is more memory-efficient. +/// Applying this rule simplifies the code and will usually save memory, but in the absence of +/// short-circuiting it may not improve performance. (It may even slightly regress performance, +/// though the difference will usually be small.) /// /// ## Examples /// ```python /// any([x.id for x in bar]) /// all([x.id for x in bar]) +/// sum([x.val for x in bar]) +/// min([x.val for x in bar]) +/// max([x.val for x in bar]) /// ``` /// /// Use instead: /// ```python /// any(x.id for x in bar) /// all(x.id for x in bar) +/// sum(x.val for x in bar) +/// min(x.val for x in bar) +/// max(x.val for x in bar) /// ``` /// /// ## Fix safety @@ -53,9 +64,9 @@ use crate::rules::flake8_comprehensions::fixes; /// rewriting some comprehensions. /// #[violation] -pub struct UnnecessaryComprehensionAnyAll; +pub struct UnnecessaryComprehensionInCall; -impl Violation for UnnecessaryComprehensionAnyAll { +impl Violation for UnnecessaryComprehensionInCall { const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; #[derive_message_formats] @@ -69,7 +80,7 @@ impl Violation for UnnecessaryComprehensionAnyAll { } /// C419 -pub(crate) fn unnecessary_comprehension_any_all( +pub(crate) fn unnecessary_comprehension_in_call( checker: &mut Checker, expr: &Expr, func: &Expr, @@ -79,10 +90,13 @@ pub(crate) fn unnecessary_comprehension_any_all( if !keywords.is_empty() { return; } + let Expr::Name(ast::ExprName { id, .. }) = func else { return; }; - if !matches!(id.as_str(), "all" | "any") { + if !(matches!(id.as_str(), "any" | "all") + || (checker.settings.preview.is_enabled() && matches!(id.as_str(), "sum" | "min" | "max"))) + { return; } let [arg] = args else { @@ -100,9 +114,9 @@ pub(crate) fn unnecessary_comprehension_any_all( return; } - let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionAnyAll, arg.range()); + let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionInCall, arg.range()); diagnostic.try_set_fix(|| { - fixes::fix_unnecessary_comprehension_any_all(expr, checker.locator(), checker.stylist()) + fixes::fix_unnecessary_comprehension_in_call(expr, checker.locator(), checker.stylist()) }); checker.diagnostics.push(diagnostic); } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419.py.snap index 4fd150bc02551..026bbd7fe75ef 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419.py.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419.py.snap @@ -98,67 +98,65 @@ C419.py:9:5: C419 [*] Unnecessary list comprehension 11 11 | # OK 12 12 | all(x.id for x in bar) -C419.py:24:5: C419 [*] Unnecessary list comprehension +C419.py:28:5: C419 [*] Unnecessary list comprehension | -22 | # Special comment handling -23 | any( -24 | [ # lbracket comment +26 | # Special comment handling +27 | any( +28 | [ # lbracket comment | _____^ -25 | | # second line comment -26 | | i.bit_count() -27 | | # random middle comment -28 | | for i in range(5) # rbracket comment -29 | | ] # rpar comment +29 | | # second line comment +30 | | i.bit_count() +31 | | # random middle comment +32 | | for i in range(5) # rbracket comment +33 | | ] # rpar comment | |_____^ C419 -30 | # trailing comment -31 | ) +34 | # trailing comment +35 | ) | = help: Remove unnecessary list comprehension ℹ Unsafe fix -21 21 | -22 22 | # Special comment handling -23 23 | any( -24 |- [ # lbracket comment -25 |- # second line comment -26 |- i.bit_count() - 24 |+ # lbracket comment - 25 |+ # second line comment - 26 |+ i.bit_count() -27 27 | # random middle comment -28 |- for i in range(5) # rbracket comment -29 |- ] # rpar comment - 28 |+ for i in range(5) # rbracket comment # rpar comment -30 29 | # trailing comment -31 30 | ) -32 31 | +25 25 | +26 26 | # Special comment handling +27 27 | any( +28 |- [ # lbracket comment +29 |- # second line comment +30 |- i.bit_count() + 28 |+ # lbracket comment + 29 |+ # second line comment + 30 |+ i.bit_count() +31 31 | # random middle comment +32 |- for i in range(5) # rbracket comment +33 |- ] # rpar comment + 32 |+ for i in range(5) # rbracket comment # rpar comment +34 33 | # trailing comment +35 34 | ) +36 35 | -C419.py:35:5: C419 [*] Unnecessary list comprehension +C419.py:39:5: C419 [*] Unnecessary list comprehension | -33 | # Weird case where the function call, opening bracket, and comment are all -34 | # on the same line. -35 | any([ # lbracket comment +37 | # Weird case where the function call, opening bracket, and comment are all +38 | # on the same line. +39 | any([ # lbracket comment | _____^ -36 | | # second line comment -37 | | i.bit_count() for i in range(5) # rbracket comment -38 | | ] # rpar comment +40 | | # second line comment +41 | | i.bit_count() for i in range(5) # rbracket comment +42 | | ] # rpar comment | |_____^ C419 -39 | ) +43 | ) | = help: Remove unnecessary list comprehension ℹ Unsafe fix -32 32 | -33 33 | # Weird case where the function call, opening bracket, and comment are all -34 34 | # on the same line. -35 |-any([ # lbracket comment -36 |- # second line comment -37 |- i.bit_count() for i in range(5) # rbracket comment -38 |- ] # rpar comment - 35 |+any( - 36 |+# lbracket comment - 37 |+# second line comment - 38 |+i.bit_count() for i in range(5) # rbracket comment # rpar comment -39 39 | ) - - +36 36 | +37 37 | # Weird case where the function call, opening bracket, and comment are all +38 38 | # on the same line. +39 |-any([ # lbracket comment +40 |- # second line comment +41 |- i.bit_count() for i in range(5) # rbracket comment +42 |- ] # rpar comment + 39 |+any( + 40 |+# lbracket comment + 41 |+# second line comment + 42 |+i.bit_count() for i in range(5) # rbracket comment # rpar comment +43 43 | ) diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419_2.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419_2.py.snap new file mode 100644 index 0000000000000..d9845b4ae92f9 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C419_C419_2.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__preview__C419_C419_1.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__preview__C419_C419_1.py.snap new file mode 100644 index 0000000000000..559a6bed9ef02 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__preview__C419_C419_1.py.snap @@ -0,0 +1,55 @@ +--- +source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs +--- +C419_1.py:1:5: C419 [*] Unnecessary list comprehension + | +1 | sum([x.val for x in bar]) + | ^^^^^^^^^^^^^^^^^^^^ C419 +2 | min([x.val for x in bar]) +3 | max([x.val for x in bar]) + | + = help: Remove unnecessary list comprehension + +ℹ Unsafe fix +1 |-sum([x.val for x in bar]) + 1 |+sum(x.val for x in bar) +2 2 | min([x.val for x in bar]) +3 3 | max([x.val for x in bar]) +4 4 | + +C419_1.py:2:5: C419 [*] Unnecessary list comprehension + | +1 | sum([x.val for x in bar]) +2 | min([x.val for x in bar]) + | ^^^^^^^^^^^^^^^^^^^^ C419 +3 | max([x.val for x in bar]) + | + = help: Remove unnecessary list comprehension + +ℹ Unsafe fix +1 1 | sum([x.val for x in bar]) +2 |-min([x.val for x in bar]) + 2 |+min(x.val for x in bar) +3 3 | max([x.val for x in bar]) +4 4 | +5 5 | # Ok + +C419_1.py:3:5: C419 [*] Unnecessary list comprehension + | +1 | sum([x.val for x in bar]) +2 | min([x.val for x in bar]) +3 | max([x.val for x in bar]) + | ^^^^^^^^^^^^^^^^^^^^ C419 +4 | +5 | # Ok + | + = help: Remove unnecessary list comprehension + +ℹ Unsafe fix +1 1 | sum([x.val for x in bar]) +2 2 | min([x.val for x in bar]) +3 |-max([x.val for x in bar]) + 3 |+max(x.val for x in bar) +4 4 | +5 5 | # Ok +6 6 | sum(x.val for x in bar) From 6b4fa170970d798eae40a91181868ae9d9f28329 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 3 Apr 2024 22:34:00 +0100 Subject: [PATCH 41/54] Rework docs for pydocstyle rules (#10754) --- .../src/rules/pydocstyle/rules/sections.rs | 198 ++++++++++-------- 1 file changed, 114 insertions(+), 84 deletions(-) diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs index dbe02ee66be8c..ea02dc57309c7 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs @@ -26,13 +26,15 @@ use crate::rules::pydocstyle::settings::Convention; /// Checks for over-indented sections in docstrings. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule enforces a consistent style for docstrings with multiple +/// sections. /// -/// Each section should use consistent indentation, with the section headers -/// matching the indentation of the docstring's opening quotes, and the -/// section bodies being indented one level further. +/// Multiline docstrings are typically composed of a summary line, followed by +/// a blank line, followed by a series of sections, each with a section header +/// and a section body. The convention is that all sections should use +/// consistent indentation. In each section, the header should match the +/// indentation of the docstring's opening quotes, and the body should be +/// indented one level further. /// /// ## Example /// ```python @@ -106,15 +108,20 @@ impl AlwaysFixableViolation for SectionNotOverIndented { /// Checks for over-indented section underlines in docstrings. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule enforces a consistent style for multiline numpy-style docstrings, +/// and helps prevent incorrect syntax in docstrings using reStructuredText. /// -/// Some docstring formats (like reStructuredText) use underlines to separate -/// section bodies from section headers. +/// Multiline numpy-style docstrings are typically composed of a summary line, +/// followed by a blank line, followed by a series of sections. Each section +/// has a section header and a section body, and there should be a series of +/// underline characters in the line following the header. The underline should +/// have the same indentation as the header. /// -/// Avoid over-indenting the section underlines, as this can cause syntax -/// errors in reStructuredText. +/// This rule enforces a consistent style for multiline numpy-style docstrings +/// with sections. If your docstring uses reStructuredText, the rule also +/// helps protect against incorrect reStructuredText syntax, which would cause +/// errors if you tried to use a tool such as Sphinx to generate documentation +/// from the docstring. /// /// This rule is enabled when using the `numpy` convention, and disabled when /// using the `google` or `pep257` conventions. @@ -132,12 +139,12 @@ impl AlwaysFixableViolation for SectionNotOverIndented { /// Time spent traveling. /// /// Returns -/// ------- +/// ------- /// float /// Speed as distance divided by time. /// /// Raises -/// ------ +/// ------ /// FasterThanLightError /// If speed is greater than the speed of light. /// """ @@ -205,11 +212,12 @@ impl AlwaysFixableViolation for SectionUnderlineNotOverIndented { /// letters. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// For stylistic consistency, all section headers in a docstring should be +/// capitalized. /// -/// Section headers should be capitalized, for consistency. +/// Multiline docstrings are typically composed of a summary line, followed by +/// a blank line, followed by a series of sections. Each section typically has +/// a header and a body. /// /// ## Example /// ```python @@ -280,22 +288,24 @@ impl AlwaysFixableViolation for CapitalizeSectionName { } /// ## What it does -/// Checks that section headers in docstrings that are not followed by a -/// newline. +/// Checks for section headers in docstrings that are followed by non-newline +/// characters. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule enforces a consistent style for multiline numpy-style docstrings. /// -/// Section headers should be followed by a newline, and not by another -/// character (like a colon), for consistency. +/// Multiline numpy-style docstrings are typically composed of a summary line, +/// followed by a blank line, followed by a series of sections. Each section +/// has a section header and a section body. The section header should be +/// followed by a newline, rather than by some other character (like a colon). /// /// This rule is enabled when using the `numpy` convention, and disabled /// when using the `google` or `pep257` conventions. /// /// ## Example /// ```python +/// # The `Parameters`, `Returns` and `Raises` section headers are all followed +/// # by a colon in this function's docstring: /// def calculate_speed(distance: float, time: float) -> float: /// """Calculate speed as distance divided by time. /// @@ -380,12 +390,19 @@ impl AlwaysFixableViolation for NewLineAfterSectionName { /// underlines. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule enforces a consistent style for multiline numpy-style docstrings, +/// and helps prevent incorrect syntax in docstrings using reStructuredText. /// -/// Some docstring formats (like reStructuredText) use underlines to separate -/// section bodies from section headers. +/// Multiline numpy-style docstrings are typically composed of a summary line, +/// followed by a blank line, followed by a series of sections. Each section +/// has a section header and a section body, and the header should be followed +/// by a series of underline characters in the following line. +/// +/// This rule enforces a consistent style for multiline numpy-style docstrings +/// with sections. If your docstring uses reStructuredText, the rule also +/// helps protect against incorrect reStructuredText syntax, which would cause +/// errors if you tried to use a tool such as Sphinx to generate documentation +/// from the docstring. /// /// This rule is enabled when using the `numpy` convention, and disabled /// when using the `google` or `pep257` conventions. @@ -476,15 +493,19 @@ impl AlwaysFixableViolation for DashedUnderlineAfterSection { /// immediately following the section name. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule enforces a consistent style for multiline numpy-style docstrings, +/// and helps prevent incorrect syntax in docstrings using reStructuredText. /// -/// Some docstring formats (like reStructuredText) use underlines to separate -/// section bodies from section headers. +/// Multiline numpy-style docstrings are typically composed of a summary line, +/// followed by a blank line, followed by a series of sections. Each section +/// has a header and a body. There should be a series of underline characters +/// in the line immediately below the header. /// -/// When present, section underlines should be positioned on the line -/// immediately following the section header. +/// This rule enforces a consistent style for multiline numpy-style docstrings +/// with sections. If your docstring uses reStructuredText, the rule also +/// helps protect against incorrect reStructuredText syntax, which would cause +/// errors if you tried to use a tool such as Sphinx to generate documentation +/// from the docstring. /// /// This rule is enabled when using the `numpy` convention, and disabled /// when using the `google` or `pep257` conventions. @@ -578,15 +599,20 @@ impl AlwaysFixableViolation for SectionUnderlineAfterName { /// the corresponding section header. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule enforces a consistent style for multiline numpy-style docstrings, +/// and helps prevent incorrect syntax in docstrings using reStructuredText. /// -/// Some docstring formats (like reStructuredText) use underlines to separate -/// section bodies from section headers. +/// Multiline numpy-style docstrings are typically composed of a summary line, +/// followed by a blank line, followed by a series of sections. Each section +/// has a section header and a section body, and there should be a series of +/// underline characters in the line following the header. The length of the +/// underline should exactly match the length of the section header. /// -/// When present, section underlines should match the length of the -/// corresponding section header. +/// This rule enforces a consistent style for multiline numpy-style docstrings +/// with sections. If your docstring uses reStructuredText, the rule also +/// helps protect against incorrect reStructuredText syntax, which would cause +/// errors if you tried to use a tool such as Sphinx to generate documentation +/// from the docstring. /// /// This rule is enabled when using the `numpy` convention, and disabled /// when using the `google` or `pep257` conventions. @@ -677,13 +703,15 @@ impl AlwaysFixableViolation for SectionUnderlineMatchesSectionLength { /// line. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. -/// -/// Docstring sections should be separated by a blank line, for consistency and +/// This rule enforces consistency in your docstrings, and helps ensure /// compatibility with documentation tooling. /// +/// Multiline docstrings are typically composed of a summary line, followed by +/// a blank line, followed by a series of sections, each with a section header +/// and a section body. If a multiline numpy-style or Google-style docstring +/// consists of multiple sections, each section should be separated by a single +/// blank line. +/// /// This rule is enabled when using the `numpy` and `google` conventions, and /// disabled when using the `pep257` convention. /// @@ -768,15 +796,15 @@ impl AlwaysFixableViolation for NoBlankLineAfterSection { } /// ## What it does -/// Checks for docstring sections that are separated by a blank line. +/// Checks for docstring sections that are not separated by a blank line. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule enforces consistency in numpy-style and Google-style docstrings, +/// and helps ensure compatibility with documentation tooling. /// -/// Docstring sections should be separated by a blank line, for consistency and -/// compatibility with documentation tooling. +/// Multiline docstrings are typically composed of a summary line, followed by +/// a blank line, followed by a series of sections, each with a section header +/// and a section body. Sections should be separated by a single blank line. /// /// This rule is enabled when using the `numpy` and `google` conventions, and /// disabled when using the `pep257` convention. @@ -861,19 +889,18 @@ impl AlwaysFixableViolation for NoBlankLineBeforeSection { } /// ## What it does -/// Checks for missing blank lines after the last section of a multi-line +/// Checks for missing blank lines after the last section of a multiline /// docstring. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by +/// This rule enforces a consistent style for multiline docstrings. +/// +/// Multiline docstrings are typically composed of a summary line, followed by /// a blank line, followed by a series of sections, each with a section header /// and a section body. /// -/// In some projects, the last section of a docstring is followed by a blank line, -/// for consistency and compatibility. -/// /// This rule may not apply to all projects; its applicability is a matter of -/// convention. By default, this rule is disabled when using the `google`, +/// convention. By default, the rule is disabled when using the `google`, /// `numpy`, and `pep257` conventions. /// /// ## Example @@ -957,15 +984,16 @@ impl AlwaysFixableViolation for BlankLineAfterLastSection { } /// ## What it does -/// Checks for docstrings that contain empty sections. +/// Checks for docstrings with empty sections. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// An empty section in a multiline docstring likely indicates an unfinished +/// or incomplete docstring. /// -/// Empty docstring sections are indicative of missing documentation. Empty -/// sections should either be removed or filled in with relevant documentation. +/// Multiline docstrings are typically composed of a summary line, followed by +/// a blank line, followed by a series of sections, each with a section header +/// and a section body. Each section body should be non-empty; empty sections +/// should either have content added to them, or be removed entirely. /// /// ## Example /// ```python @@ -1046,13 +1074,14 @@ impl Violation for EmptyDocstringSection { /// Checks for docstring section headers that do not end with a colon. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by +/// This rule enforces a consistent style for multiline Google-style +/// docstrings. If a multiline Google-style docstring consists of multiple +/// sections, each section header should end with a colon. +/// +/// Multiline docstrings are typically composed of a summary line, followed by /// a blank line, followed by a series of sections, each with a section header /// and a section body. /// -/// In a docstring, each section header should end with a colon, for -/// consistency. -/// /// This rule is enabled when using the `google` convention, and disabled when /// using the `pep257` and `numpy` conventions. /// @@ -1128,13 +1157,14 @@ impl AlwaysFixableViolation for SectionNameEndsInColon { /// parameters in the function. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by -/// a blank line, followed by a series of sections, each with a section header -/// and a section body. +/// This rule helps prevent you from leaving Google-style docstrings unfinished +/// or incomplete. Multiline Google-style docstrings should describe all +/// parameters for the function they are documenting. /// -/// Function docstrings often include a section for function arguments, which -/// should include documentation for every argument. Undocumented arguments are -/// indicative of missing documentation. +/// Multiline docstrings are typically composed of a summary line, followed by +/// a blank line, followed by a series of sections, each with a section header +/// and a section body. Function docstrings often include a section for +/// function arguments; this rule is concerned with that section only. /// /// This rule is enabled when using the `google` convention, and disabled when /// using the `pep257` and `numpy` conventions. @@ -1210,17 +1240,17 @@ impl Violation for UndocumentedParam { } /// ## What it does -/// Checks for docstring sections that contain blank lines between the section -/// header and the section body. +/// Checks for docstring sections that contain blank lines between a section +/// header and a section body. /// /// ## Why is this bad? -/// Multi-line docstrings are typically composed of a summary line, followed by +/// This rule enforces a consistent style for multiline docstrings. +/// +/// Multiline docstrings are typically composed of a summary line, followed by /// a blank line, followed by a series of sections, each with a section header +/// and a section body. There should be no blank lines between a section header /// and a section body. /// -/// Docstring sections should not contain blank lines between the section header -/// and the section body, for consistency. -/// /// ## Example /// ```python /// def calculate_speed(distance: float, time: float) -> float: From d02b1069b59d4520d21a7940b43947c98547a217 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 4 Apr 2024 09:08:48 +0530 Subject: [PATCH 42/54] Add semantic model flag when inside f-string replacement field (#10766) ## Summary This PR adds a new semantic model flag to indicate that the checker is inside an f-string replacement field. This will be used to ignore certain checks if the target version doesn't support a specific feature like PEP 701. fixes: #10761 ## Test Plan Add a test case from the raised issue. --- .../test/fixtures/flake8_quotes/singles.py | 2 ++ crates/ruff_linter/src/checkers/ast/mod.rs | 13 +++++++++++-- .../flake8_quotes/rules/check_string_quotes.rs | 9 ++------- crates/ruff_python_semantic/src/model.rs | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles.py b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles.py index e9a96a105cb6e..06aa7504f7140 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles.py @@ -5,3 +5,5 @@ # https://github.com/astral-sh/ruff/issues/10546 x: "Literal['foo', 'bar']" +# https://github.com/astral-sh/ruff/issues/10761 +f"Before {f'x {x}' if y else f'foo {z}'} after" diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 62de2dfe0d0e4..6af274cb8f331 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -32,8 +32,8 @@ use itertools::Itertools; use log::debug; use ruff_python_ast::{ self as ast, all::DunderAllName, Comprehension, ElifElseClause, ExceptHandler, Expr, - ExprContext, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, Pattern, Stmt, - Suite, UnaryOp, + ExprContext, FStringElement, Keyword, MatchCase, Parameter, ParameterWithDefault, Parameters, + Pattern, Stmt, Suite, UnaryOp, }; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -1580,6 +1580,15 @@ impl<'a> Visitor<'a> for Checker<'a> { .push((bound, self.semantic.snapshot())); } } + + fn visit_f_string_element(&mut self, f_string_element: &'a FStringElement) { + let snapshot = self.semantic.flags; + if f_string_element.is_expression() { + self.semantic.flags |= SemanticModelFlags::F_STRING_REPLACEMENT_FIELD; + } + visitor::walk_f_string_element(self, f_string_element); + self.semantic.flags = snapshot; + } } impl<'a> Checker<'a> { diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs index fc4ff375053ec..7c01db8c5af8f 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs @@ -449,13 +449,8 @@ pub(crate) fn check_string_quotes(checker: &mut Checker, string_like: StringLike return; } - // If the string is part of a f-string, ignore it. - if checker - .indexer() - .fstring_ranges() - .outermost(string_like.start()) - .is_some_and(|outer| outer.start() < string_like.start() && string_like.end() < outer.end()) - { + // TODO(dhruvmanila): Support checking for escaped quotes in f-strings. + if checker.semantic().in_f_string_replacement_field() { return; } diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 07dce98d1e919..ac735d97acd29 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -1525,6 +1525,12 @@ impl<'a> SemanticModel<'a> { self.flags.intersects(SemanticModelFlags::F_STRING) } + /// Return `true` if the model is in an f-string replacement field. + pub const fn in_f_string_replacement_field(&self) -> bool { + self.flags + .intersects(SemanticModelFlags::F_STRING_REPLACEMENT_FIELD) + } + /// Return `true` if the model is in boolean test. pub const fn in_boolean_test(&self) -> bool { self.flags.intersects(SemanticModelFlags::BOOLEAN_TEST) @@ -1960,6 +1966,15 @@ bitflags! { /// ``` const DUNDER_ALL_DEFINITION = 1 << 22; + /// The model is in an f-string replacement field. + /// + /// For example, the model could be visiting `x` or `y` in: + /// + /// ```python + /// f"first {x} second {y}" + /// ``` + const F_STRING_REPLACEMENT_FIELD = 1 << 23; + /// The context is in any type annotation. const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits(); From fd8da66fcbde36e78456829283bf9220be267806 Mon Sep 17 00:00:00 2001 From: NotWearingPants <26556598+NotWearingPants@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:50:55 +0300 Subject: [PATCH 43/54] docs: `Lint` -> `Format` in formatter.md (#10777) ## Summary Since #10217 the [formatter docs](https://docs.astral.sh/ruff/formatter/) contained ``` ruff format # Format all files in the current directory. ruff format path/to/code/ # Lint all files in `path/to/code` (and any subdirectories). ruff format path/to/file.py # Format a single file. ``` I believe the `Lint` here is a copy-paste typo from the [linter docs](https://docs.astral.sh/ruff/linter/). ## Test Plan N/A --- docs/formatter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/formatter.md b/docs/formatter.md index 04d04b14eaf3f..c85b8c82c42c5 100644 --- a/docs/formatter.md +++ b/docs/formatter.md @@ -12,7 +12,7 @@ directories, and formats all discovered Python files: ```shell ruff format # Format all files in the current directory. -ruff format path/to/code/ # Lint all files in `path/to/code` (and any subdirectories). +ruff format path/to/code/ # Format all files in `path/to/code` (and any subdirectories). ruff format path/to/file.py # Format a single file. ``` From d050d6da2e4d807297afe23808a283ff25c90868 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Thu, 4 Apr 2024 15:20:50 -0700 Subject: [PATCH 44/54] `ruff server` now supports the `source.organizeImports` source action (#10652) ## Summary This builds on top of the work in https://github.com/astral-sh/ruff/pull/10597 to support `Ruff: Organize imports` as an available source action. To do this, we have to support `Clone`-ing for linter settings, since we need to modify them in place to select import-related diagnostics specifically (`I001` and `I002`). ## Test Plan https://github.com/astral-sh/ruff/assets/19577865/04282d01-dfda-4ac5-aa8f-6a92d5f85bfd --- .../src/rules/flake8_annotations/settings.rs | 2 +- .../src/rules/flake8_bandit/settings.rs | 2 +- .../src/rules/flake8_boolean_trap/settings.rs | 2 +- .../src/rules/flake8_bugbear/settings.rs | 2 +- .../src/rules/flake8_builtins/settings.rs | 2 +- .../rules/flake8_comprehensions/settings.rs | 2 +- .../src/rules/flake8_copyright/settings.rs | 2 +- .../src/rules/flake8_errmsg/settings.rs | 2 +- .../src/rules/flake8_gettext/settings.rs | 2 +- .../flake8_implicit_str_concat/settings.rs | 2 +- .../flake8_import_conventions/settings.rs | 2 +- .../src/rules/flake8_pytest_style/settings.rs | 2 +- .../src/rules/flake8_quotes/settings.rs | 2 +- .../src/rules/flake8_self/settings.rs | 2 +- .../src/rules/flake8_tidy_imports/settings.rs | 2 +- .../rules/flake8_type_checking/settings.rs | 2 +- .../rules/flake8_unused_arguments/settings.rs | 2 +- .../ruff_linter/src/rules/isort/categorize.rs | 2 +- .../ruff_linter/src/rules/isort/settings.rs | 2 +- .../ruff_linter/src/rules/mccabe/settings.rs | 2 +- .../src/rules/pep8_naming/settings.rs | 4 +- .../src/rules/pycodestyle/settings.rs | 2 +- .../src/rules/pydocstyle/settings.rs | 2 +- .../src/rules/pyflakes/settings.rs | 2 +- .../ruff_linter/src/rules/pylint/settings.rs | 2 +- .../src/rules/pyupgrade/settings.rs | 2 +- .../src/settings/fix_safety_table.rs | 2 +- crates/ruff_linter/src/settings/mod.rs | 2 +- crates/ruff_linter/src/settings/rule_table.rs | 2 +- crates/ruff_linter/src/settings/types.rs | 2 +- crates/ruff_server/src/server.rs | 2 +- .../src/server/api/requests/code_action.rs | 41 +++++++++++++++++-- .../api/requests/code_action_resolve.rs | 40 ++++++++++++++++-- 33 files changed, 106 insertions(+), 39 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_annotations/settings.rs b/crates/ruff_linter/src/rules/flake8_annotations/settings.rs index 011cf01f4a926..342a56023a0e1 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] #[allow(clippy::struct_excessive_bools)] pub struct Settings { pub mypy_init_return: bool, diff --git a/crates/ruff_linter/src/rules/flake8_bandit/settings.rs b/crates/ruff_linter/src/rules/flake8_bandit/settings.rs index 17a018a25c64c..ee96e6ee667d2 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/settings.rs @@ -10,7 +10,7 @@ pub fn default_tmp_dirs() -> Vec { .to_vec() } -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub hardcoded_tmp_directory: Vec, pub check_typed_exception: bool, diff --git a/crates/ruff_linter/src/rules/flake8_boolean_trap/settings.rs b/crates/ruff_linter/src/rules/flake8_boolean_trap/settings.rs index 3b88395c9847a..9825442ad55ff 100644 --- a/crates/ruff_linter/src/rules/flake8_boolean_trap/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_boolean_trap/settings.rs @@ -6,7 +6,7 @@ use ruff_macros::CacheKey; use crate::display_settings; -#[derive(Debug, CacheKey, Default)] +#[derive(Debug, Clone, CacheKey, Default)] pub struct Settings { pub extend_allowed_calls: Vec, } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs b/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs index 03c4d5cdf1d98..6a13a3c8b79ba 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub extend_immutable_calls: Vec, } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs index d3fc3a70f74bd..e11537efb7ff4 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub builtins_ignorelist: Vec, } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs index 41110886a5d4e..778d5601ef01d 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub allow_dict_calls_with_keyword_arguments: bool, } diff --git a/crates/ruff_linter/src/rules/flake8_copyright/settings.rs b/crates/ruff_linter/src/rules/flake8_copyright/settings.rs index b62c221c769fa..03551b0a0a93f 100644 --- a/crates/ruff_linter/src/rules/flake8_copyright/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_copyright/settings.rs @@ -7,7 +7,7 @@ use std::fmt::{Display, Formatter}; use crate::display_settings; use ruff_macros::CacheKey; -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub notice_rgx: Regex, pub author: Option, diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs b/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs index ec239435cee07..c51ec3a31356d 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub max_string_length: usize, } diff --git a/crates/ruff_linter/src/rules/flake8_gettext/settings.rs b/crates/ruff_linter/src/rules/flake8_gettext/settings.rs index 6e3a6c367cd10..76180b45ac59d 100644 --- a/crates/ruff_linter/src/rules/flake8_gettext/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_gettext/settings.rs @@ -2,7 +2,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub functions_names: Vec, } diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs index 90c6a9a1812fe..ab805839b67a0 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub allow_multiline: bool, } diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs index 50c5aacc67eb1..292658a6cbca0 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/settings.rs @@ -57,7 +57,7 @@ impl FromIterator for BannedAliases { } } -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub aliases: FxHashMap, pub banned_aliases: FxHashMap, diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs index cab3d2d5a3eed..50b08c48c1f91 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/settings.rs @@ -24,7 +24,7 @@ pub fn default_broad_exceptions() -> Vec { .to_vec() } -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub fixture_parentheses: bool, pub parametrize_names_type: types::ParametrizeNameType, diff --git a/crates/ruff_linter/src/rules/flake8_quotes/settings.rs b/crates/ruff_linter/src/rules/flake8_quotes/settings.rs index 5e0c93beadab0..b241e70b49350 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/settings.rs @@ -31,7 +31,7 @@ impl From for Quote { } } -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub inline_quotes: Quote, pub multiline_quotes: Quote, diff --git a/crates/ruff_linter/src/rules/flake8_self/settings.rs b/crates/ruff_linter/src/rules/flake8_self/settings.rs index c59a0ec89d221..cb3027fa90c23 100644 --- a/crates/ruff_linter/src/rules/flake8_self/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_self/settings.rs @@ -17,7 +17,7 @@ pub const IGNORE_NAMES: [&str; 7] = [ "_value_", ]; -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub ignore_names: Vec, } diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs index 8f9e29ea21aac..fee7a12482cff 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/settings.rs @@ -39,7 +39,7 @@ impl Display for Strictness { } } -#[derive(Debug, CacheKey, Default)] +#[derive(Debug, Clone, CacheKey, Default)] pub struct Settings { pub ban_relative_imports: Strictness, pub banned_api: FxHashMap, diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs b/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs index fa8214e5b74af..11f5f87e500cf 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub strict: bool, pub exempt_modules: Vec, diff --git a/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs b/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs index 7e13bc6495a9a..5cd02d876a890 100644 --- a/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_unused_arguments/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub ignore_variadic_names: bool, } diff --git a/crates/ruff_linter/src/rules/isort/categorize.rs b/crates/ruff_linter/src/rules/isort/categorize.rs index 874070135c21e..7f5a10bfd0983 100644 --- a/crates/ruff_linter/src/rules/isort/categorize.rs +++ b/crates/ruff_linter/src/rules/isort/categorize.rs @@ -270,7 +270,7 @@ pub(crate) fn categorize_imports<'a>( block_by_type } -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct KnownModules { /// A map of known modules to their section. known: Vec<(glob::Pattern, ImportSection)>, diff --git a/crates/ruff_linter/src/rules/isort/settings.rs b/crates/ruff_linter/src/rules/isort/settings.rs index 8ae6464932123..7307b6664a08d 100644 --- a/crates/ruff_linter/src/rules/isort/settings.rs +++ b/crates/ruff_linter/src/rules/isort/settings.rs @@ -44,7 +44,7 @@ impl Display for RelativeImportsOrder { } } -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] #[allow(clippy::struct_excessive_bools)] pub struct Settings { pub required_imports: BTreeSet, diff --git a/crates/ruff_linter/src/rules/mccabe/settings.rs b/crates/ruff_linter/src/rules/mccabe/settings.rs index 65abe3c91d1a0..d5e2db45f73a4 100644 --- a/crates/ruff_linter/src/rules/mccabe/settings.rs +++ b/crates/ruff_linter/src/rules/mccabe/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt::{Display, Formatter}; -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub max_complexity: usize, } diff --git a/crates/ruff_linter/src/rules/pep8_naming/settings.rs b/crates/ruff_linter/src/rules/pep8_naming/settings.rs index 7ad3830c8107f..9705b7cde05d5 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/settings.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/settings.rs @@ -11,7 +11,7 @@ use ruff_macros::CacheKey; use crate::display_settings; -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub ignore_names: IgnoreNames, pub classmethod_decorators: Vec, @@ -85,7 +85,7 @@ static DEFAULTS: &[&str] = &[ "maxDiff", ]; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum IgnoreNames { Default, UserProvided { diff --git a/crates/ruff_linter/src/rules/pycodestyle/settings.rs b/crates/ruff_linter/src/rules/pycodestyle/settings.rs index 1ce1d1c029ea6..b034a778740a7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/settings.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/settings.rs @@ -6,7 +6,7 @@ use std::fmt; use crate::line_width::LineLength; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub max_line_length: LineLength, pub max_doc_length: Option, diff --git a/crates/ruff_linter/src/rules/pydocstyle/settings.rs b/crates/ruff_linter/src/rules/pydocstyle/settings.rs index 1b3a177af64eb..974c8742f9ec0 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/settings.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/settings.rs @@ -83,7 +83,7 @@ impl fmt::Display for Convention { } } -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub convention: Option, pub ignore_decorators: BTreeSet, diff --git a/crates/ruff_linter/src/rules/pyflakes/settings.rs b/crates/ruff_linter/src/rules/pyflakes/settings.rs index b87c9aebf2108..2aa404fbe4688 100644 --- a/crates/ruff_linter/src/rules/pyflakes/settings.rs +++ b/crates/ruff_linter/src/rules/pyflakes/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub extend_generics: Vec, } diff --git a/crates/ruff_linter/src/rules/pylint/settings.rs b/crates/ruff_linter/src/rules/pylint/settings.rs index c98698d5a283c..383f5136c8de0 100644 --- a/crates/ruff_linter/src/rules/pylint/settings.rs +++ b/crates/ruff_linter/src/rules/pylint/settings.rs @@ -48,7 +48,7 @@ impl fmt::Display for ConstantType { } } -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct Settings { pub allow_magic_value_types: Vec, pub allow_dunder_method_names: FxHashSet, diff --git a/crates/ruff_linter/src/rules/pyupgrade/settings.rs b/crates/ruff_linter/src/rules/pyupgrade/settings.rs index 4e228351f3639..72fedbdd339c7 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/settings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/settings.rs @@ -4,7 +4,7 @@ use crate::display_settings; use ruff_macros::CacheKey; use std::fmt; -#[derive(Debug, Default, CacheKey)] +#[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { pub keep_runtime_typing: bool, } diff --git a/crates/ruff_linter/src/settings/fix_safety_table.rs b/crates/ruff_linter/src/settings/fix_safety_table.rs index e3b280be0cddc..8f4382452f3c0 100644 --- a/crates/ruff_linter/src/settings/fix_safety_table.rs +++ b/crates/ruff_linter/src/settings/fix_safety_table.rs @@ -14,7 +14,7 @@ use crate::{ /// A table to keep track of which rules fixes should have /// their safety overridden. -#[derive(Debug, CacheKey, Default)] +#[derive(Debug, Clone, CacheKey, Default)] pub struct FixSafetyTable { forced_safe: RuleSet, forced_unsafe: RuleSet, diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 510dc3a512634..42a4ab88b8c19 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -206,7 +206,7 @@ macro_rules! display_settings { }; } -#[derive(Debug, CacheKey)] +#[derive(Debug, Clone, CacheKey)] pub struct LinterSettings { pub exclude: FilePatternSet, pub extension: ExtensionMapping, diff --git a/crates/ruff_linter/src/settings/rule_table.rs b/crates/ruff_linter/src/settings/rule_table.rs index f6b8482afc71c..2e598cbf44d36 100644 --- a/crates/ruff_linter/src/settings/rule_table.rs +++ b/crates/ruff_linter/src/settings/rule_table.rs @@ -6,7 +6,7 @@ use ruff_macros::CacheKey; use crate::registry::{Rule, RuleSet, RuleSetIterator}; /// A table to keep track of which rules are enabled and whether they should be fixed. -#[derive(Debug, CacheKey, Default)] +#[derive(Debug, Clone, CacheKey, Default)] pub struct RuleTable { /// Maps rule codes to a boolean indicating if the rule should be fixed. enabled: RuleSet, diff --git a/crates/ruff_linter/src/settings/types.rs b/crates/ruff_linter/src/settings/types.rs index 4e4c8fbf978c5..99aec6740cab2 100644 --- a/crates/ruff_linter/src/settings/types.rs +++ b/crates/ruff_linter/src/settings/types.rs @@ -590,7 +590,7 @@ impl Display for RequiredVersion { /// pattern matching. pub type IdentifierPattern = glob::Pattern; -#[derive(Debug, CacheKey, Default)] +#[derive(Debug, Clone, CacheKey, Default)] pub struct PerFileIgnores { // Ordered as (absolute path matcher, basename matcher, rules) ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>, diff --git a/crates/ruff_server/src/server.rs b/crates/ruff_server/src/server.rs index 53bafa06a49ae..4f28dbd81a1fe 100644 --- a/crates/ruff_server/src/server.rs +++ b/crates/ruff_server/src/server.rs @@ -271,7 +271,7 @@ impl SupportedCodeAction { [ Self::QuickFix, Self::SourceFixAll, - // Self::SourceOrganizeImports, + Self::SourceOrganizeImports, ] .into_iter() } diff --git a/crates/ruff_server/src/server/api/requests/code_action.rs b/crates/ruff_server/src/server/api/requests/code_action.rs index a16eab75af9c3..e9bc8d1842d85 100644 --- a/crates/ruff_server/src/server/api/requests/code_action.rs +++ b/crates/ruff_server/src/server/api/requests/code_action.rs @@ -9,7 +9,7 @@ use lsp_types::{self as types, request as req}; use rustc_hash::FxHashSet; use types::{CodeActionKind, CodeActionOrCommand}; -use super::code_action_resolve::resolve_edit_for_fix_all; +use super::code_action_resolve::{resolve_edit_for_fix_all, resolve_edit_for_organize_imports}; pub(crate) struct CodeActions; @@ -26,11 +26,11 @@ impl super::BackgroundDocumentRequestHandler for CodeActions { ) -> Result> { let mut response: types::CodeActionResponse = types::CodeActionResponse::default(); - let supported_code_actions = supported_code_actions(params.context.only); + let supported_code_actions = supported_code_actions(params.context.only.clone()); if supported_code_actions.contains(&SupportedCodeAction::QuickFix) { response.extend( - quick_fix(&snapshot, params.context.diagnostics) + quick_fix(&snapshot, params.context.diagnostics.clone()) .with_failure_code(ErrorCode::InternalError)?, ); } @@ -40,7 +40,7 @@ impl super::BackgroundDocumentRequestHandler for CodeActions { } if supported_code_actions.contains(&SupportedCodeAction::SourceOrganizeImports) { - todo!("Implement the `source.organizeImports` code action"); + response.push(organize_imports(&snapshot).with_failure_code(ErrorCode::InternalError)?); } Ok(Some(response)) @@ -109,6 +109,39 @@ fn fix_all(snapshot: &DocumentSnapshot) -> crate::Result { Ok(types::CodeActionOrCommand::CodeAction(action)) } +fn organize_imports(snapshot: &DocumentSnapshot) -> crate::Result { + let document = snapshot.document(); + + let (edit, data) = if snapshot + .resolved_client_capabilities() + .code_action_deferred_edit_resolution + { + // The edit will be resolved later in the `CodeActionsResolve` request + ( + None, + Some(serde_json::to_value(snapshot.url()).expect("document url to serialize")), + ) + } else { + ( + Some(resolve_edit_for_organize_imports( + document, + snapshot.url(), + &snapshot.configuration().linter, + snapshot.encoding(), + )?), + None, + ) + }; + let action = types::CodeAction { + title: format!("{DIAGNOSTIC_NAME}: Organize imports"), + kind: Some(types::CodeActionKind::SOURCE_ORGANIZE_IMPORTS), + edit, + data, + ..Default::default() + }; + Ok(types::CodeActionOrCommand::CodeAction(action)) +} + /// If `action_filter` is `None`, this returns [`SupportedCodeActionKind::all()`]. Otherwise, /// the list is filtered. fn supported_code_actions( diff --git a/crates/ruff_server/src/server/api/requests/code_action_resolve.rs b/crates/ruff_server/src/server/api/requests/code_action_resolve.rs index ffb05c621062f..326c99ecb9e97 100644 --- a/crates/ruff_server/src/server/api/requests/code_action_resolve.rs +++ b/crates/ruff_server/src/server/api/requests/code_action_resolve.rs @@ -7,6 +7,7 @@ use crate::session::DocumentSnapshot; use crate::PositionEncoding; use lsp_server::ErrorCode; use lsp_types::{self as types, request as req}; +use ruff_linter::codes::Rule; use ruff_linter::settings::LinterSettings; pub(crate) struct CodeActionResolve; @@ -47,9 +48,15 @@ impl super::BackgroundDocumentRequestHandler for CodeActionResolve { ) .with_failure_code(ErrorCode::InternalError)?, ), - SupportedCodeAction::SourceOrganizeImports => { - todo!("Support `source.organizeImports`") - } + SupportedCodeAction::SourceOrganizeImports => Some( + resolve_edit_for_organize_imports( + document, + snapshot.url(), + &snapshot.configuration().linter, + snapshot.encoding(), + ) + .with_failure_code(ErrorCode::InternalError)?, + ), SupportedCodeAction::QuickFix => { return Err(anyhow::anyhow!( "Got a code action that should not need additional resolution: {action_kind:?}" @@ -80,3 +87,30 @@ pub(super) fn resolve_edit_for_fix_all( ..Default::default() }) } + +pub(super) fn resolve_edit_for_organize_imports( + document: &crate::edit::Document, + url: &types::Url, + linter_settings: &ruff_linter::settings::LinterSettings, + encoding: PositionEncoding, +) -> crate::Result { + let mut linter_settings = linter_settings.clone(); + linter_settings.rules = [ + Rule::UnsortedImports, // I001 + Rule::MissingRequiredImport, // I002 + ] + .into_iter() + .collect(); + + Ok(types::WorkspaceEdit { + changes: Some( + [( + url.clone(), + crate::fix::fix_all(document, &linter_settings, encoding)?, + )] + .into_iter() + .collect(), + ), + ..Default::default() + }) +} From 2e7a1a4cb15cf15eb287c51a369a4e72414504cb Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 5 Apr 2024 08:42:00 +0200 Subject: [PATCH 45/54] D403: Require capitalizing single word sentence (#10776) --- .../test/fixtures/pydocstyle/D403.py | 6 ++++ .../src/rules/pydocstyle/rules/capitalized.rs | 30 +++++++++++------ ...ules__pydocstyle__tests__D403_D403.py.snap | 33 +++++++++++++++++++ 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py index d90acb358efab..bd90d60bc84c2 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py @@ -25,3 +25,9 @@ def non_ascii(): def all_caps(): """th•s is not capitalized.""" + +def single_word(): + """singleword.""" + +def single_word_no_dot(): + """singleword""" diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs index 7988490f6c18f..88f02716764d0 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs @@ -59,26 +59,36 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) { } let body = docstring.body(); - let Some(first_word) = body.split(' ').next() else { - return; - }; - - // Like pydocstyle, we only support ASCII for now. - for char in first_word.chars() { - if !char.is_ascii_alphabetic() && char != '\'' { - return; - } - } + let first_word = body.split_once(' ').map_or_else( + || { + // If the docstring is a single word, trim the punctuation marks because + // it makes the ASCII test below fail. + body.trim_end_matches(['.', '!', '?']) + }, + |(first_word, _)| first_word, + ); let mut first_word_chars = first_word.chars(); let Some(first_char) = first_word_chars.next() else { return; }; + + if !first_char.is_ascii() { + return; + } + let uppercase_first_char = first_char.to_ascii_uppercase(); if first_char == uppercase_first_char { return; } + // Like pydocstyle, we only support ASCII for now. + for char in first_word.chars().skip(1) { + if !char.is_ascii_alphabetic() && char != '\'' { + return; + } + } + let capitalized_word = uppercase_first_char.to_string() + first_word_chars.as_str(); let mut diagnostic = Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap index 0ac0eed67f2ff..07a0589fb11fc 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap @@ -19,4 +19,37 @@ D403.py:2:5: D403 [*] First word of the first line should be capitalized: `this` 4 4 | def good_function(): 5 5 | """This docstring is capitalized.""" +D403.py:30:5: D403 [*] First word of the first line should be capitalized: `singleword` -> `Singleword` + | +29 | def single_word(): +30 | """singleword.""" + | ^^^^^^^^^^^^^^^^^ D403 +31 | +32 | def single_word_no_dot(): + | + = help: Capitalize `singleword` to `Singleword` +ℹ Safe fix +27 27 | """th•s is not capitalized.""" +28 28 | +29 29 | def single_word(): +30 |- """singleword.""" + 30 |+ """Singleword.""" +31 31 | +32 32 | def single_word_no_dot(): +33 33 | """singleword""" + +D403.py:33:5: D403 [*] First word of the first line should be capitalized: `singleword` -> `Singleword` + | +32 | def single_word_no_dot(): +33 | """singleword""" + | ^^^^^^^^^^^^^^^^ D403 + | + = help: Capitalize `singleword` to `Singleword` + +ℹ Safe fix +30 30 | """singleword.""" +31 31 | +32 32 | def single_word_no_dot(): +33 |- """singleword""" + 33 |+ """Singleword""" From c2790f912b3c4c02813933f3ddbe5e4c10afe931 Mon Sep 17 00:00:00 2001 From: Auguste Lalande Date: Fri, 5 Apr 2024 17:33:39 -0400 Subject: [PATCH 46/54] [`pylint`] Implement `bad-staticmethod-argument` (`PLW0211`) (#10781) ## Summary Implement `bad-staticmethod-argument` from pylint, part of #970. ## Test Plan Text fixture added. --- .../pylint/bad_staticmethod_argument.py | 44 ++++++++ .../checkers/ast/analyze/deferred_scopes.rs | 15 ++- crates/ruff_linter/src/codes.rs | 13 ++- crates/ruff_linter/src/rules/pylint/mod.rs | 4 + .../pylint/rules/bad_staticmethod_argument.rs | 103 ++++++++++++++++++ .../ruff_linter/src/rules/pylint/rules/mod.rs | 2 + ..._PLW0211_bad_staticmethod_argument.py.snap | 28 +++++ ruff.schema.json | 2 + 8 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pylint/bad_staticmethod_argument.py create mode 100644 crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs create mode 100644 crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/bad_staticmethod_argument.py b/crates/ruff_linter/resources/test/fixtures/pylint/bad_staticmethod_argument.py new file mode 100644 index 0000000000000..6de4d74ee485f --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/bad_staticmethod_argument.py @@ -0,0 +1,44 @@ +class Wolf: + @staticmethod + def eat(self): # [bad-staticmethod-argument] + pass + + +class Wolf: + @staticmethod + def eat(sheep): + pass + + +class Sheep: + @staticmethod + def eat(cls, x, y, z): # [bad-staticmethod-argument] + pass + + @staticmethod + def sleep(self, x, y, z): # [bad-staticmethod-argument] + pass + + def grow(self, x, y, z): + pass + + @classmethod + def graze(cls, x, y, z): + pass + + +class Foo: + @staticmethod + def eat(x, self, z): + pass + + @staticmethod + def sleep(x, cls, z): + pass + + def grow(self, x, y, z): + pass + + @classmethod + def graze(cls, x, y, z): + pass diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index 0a85c041b0bdf..c4d0ee7944b78 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -15,15 +15,19 @@ use crate::rules::{ pub(crate) fn deferred_scopes(checker: &mut Checker) { if !checker.any_enabled(&[ Rule::AsyncioDanglingTask, + Rule::BadStaticmethodArgument, + Rule::BuiltinAttributeShadowing, Rule::GlobalVariableNotAssigned, Rule::ImportPrivateName, Rule::ImportShadowedByLoopVar, - Rule::InvalidFirstArgumentNameForMethod, Rule::InvalidFirstArgumentNameForClassMethod, + Rule::InvalidFirstArgumentNameForMethod, Rule::NoSelfUse, Rule::RedefinedArgumentFromLocal, Rule::RedefinedWhileUnused, Rule::RuntimeImportInTypeCheckingBlock, + Rule::SingledispatchMethod, + Rule::SingledispatchmethodFunction, Rule::TooManyLocals, Rule::TypingOnlyFirstPartyImport, Rule::TypingOnlyStandardLibraryImport, @@ -31,19 +35,16 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { Rule::UndefinedLocal, Rule::UnusedAnnotation, Rule::UnusedClassMethodArgument, - Rule::BuiltinAttributeShadowing, Rule::UnusedFunctionArgument, Rule::UnusedImport, Rule::UnusedLambdaArgument, Rule::UnusedMethodArgument, Rule::UnusedPrivateProtocol, Rule::UnusedPrivateTypeAlias, - Rule::UnusedPrivateTypeVar, Rule::UnusedPrivateTypedDict, + Rule::UnusedPrivateTypeVar, Rule::UnusedStaticMethodArgument, Rule::UnusedVariable, - Rule::SingledispatchMethod, - Rule::SingledispatchmethodFunction, ]) { return; } @@ -424,6 +425,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { pylint::rules::singledispatchmethod_function(checker, scope, &mut diagnostics); } + if checker.enabled(Rule::BadStaticmethodArgument) { + pylint::rules::bad_staticmethod_argument(checker, scope, &mut diagnostics); + } + if checker.any_enabled(&[ Rule::InvalidFirstArgumentNameForClassMethod, Rule::InvalidFirstArgumentNameForMethod, diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 75cb2966bc30f..10a7fb752592f 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -225,12 +225,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet), (Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias), (Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel), - (Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName), - (Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName), - (Pylint, "C2801") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDunderCall), #[allow(deprecated)] (Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString), + (Pylint, "C2401") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiName), + (Pylint, "C2403") => (RuleGroup::Preview, rules::pylint::rules::NonAsciiImportName), (Pylint, "C2701") => (RuleGroup::Preview, rules::pylint::rules::ImportPrivateName), + (Pylint, "C2801") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDunderCall), (Pylint, "C3002") => (RuleGroup::Stable, rules::pylint::rules::UnnecessaryDirectLambdaCall), (Pylint, "E0100") => (RuleGroup::Stable, rules::pylint::rules::YieldInInit), (Pylint, "E0101") => (RuleGroup::Stable, rules::pylint::rules::ReturnInInit), @@ -272,6 +272,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "R0203") => (RuleGroup::Preview, rules::pylint::rules::NoStaticmethodDecorator), (Pylint, "R0206") => (RuleGroup::Stable, rules::pylint::rules::PropertyWithParameters), (Pylint, "R0402") => (RuleGroup::Stable, rules::pylint::rules::ManualFromImport), + (Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods), (Pylint, "R0911") => (RuleGroup::Stable, rules::pylint::rules::TooManyReturnStatements), (Pylint, "R0912") => (RuleGroup::Stable, rules::pylint::rules::TooManyBranches), (Pylint, "R0913") => (RuleGroup::Stable, rules::pylint::rules::TooManyArguments), @@ -282,9 +283,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls), (Pylint, "R1702") => (RuleGroup::Preview, rules::pylint::rules::TooManyNestedBlocks), (Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal), + (Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary), (Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn), (Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison), - (Pylint, "R1706") => (RuleGroup::Removed, rules::pylint::rules::AndOrTernary), (Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias), (Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup), (Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup), @@ -302,11 +303,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral), (Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext), (Pylint, "W0133") => (RuleGroup::Preview, rules::pylint::rules::UselessExceptionStatement), + (Pylint, "W0211") => (RuleGroup::Preview, rules::pylint::rules::BadStaticmethodArgument), (Pylint, "W0245") => (RuleGroup::Preview, rules::pylint::rules::SuperWithoutBrackets), (Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf), (Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned), - (Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel), (Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement), + (Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel), (Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException), (Pylint, "W1501") => (RuleGroup::Preview, rules::pylint::rules::BadOpenMode), (Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault), @@ -316,7 +318,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { #[allow(deprecated)] (Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash), (Pylint, "W2101") => (RuleGroup::Preview, rules::pylint::rules::UselessWithLock), - (Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods), (Pylint, "W2901") => (RuleGroup::Stable, rules::pylint::rules::RedefinedLoopName), #[allow(deprecated)] (Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 86d3d2e897e37..867e6dfc9596b 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -190,6 +190,10 @@ mod tests { Path::new("useless_exception_statement.py") )] #[test_case(Rule::NanComparison, Path::new("nan_comparison.py"))] + #[test_case( + Rule::BadStaticmethodArgument, + Path::new("bad_staticmethod_argument.py") + )] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs new file mode 100644 index 0000000000000..c99cd52c8cc1b --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_staticmethod_argument.rs @@ -0,0 +1,103 @@ +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast as ast; +use ruff_python_ast::ParameterWithDefault; +use ruff_python_semantic::analyze::function_type; +use ruff_python_semantic::Scope; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for static methods that use `self` or `cls` as their first argument. +/// +/// ## Why is this bad? +/// [PEP 8] recommends the use of `self` and `cls` as the first arguments for +/// instance methods and class methods, respectively. Naming the first argument +/// of a static method as `self` or `cls` can be misleading, as static methods +/// do not receive an instance or class reference as their first argument. +/// +/// ## Example +/// ```python +/// class Wolf: +/// @staticmethod +/// def eat(self): +/// pass +/// ``` +/// +/// Use instead: +/// ```python +/// class Wolf: +/// @staticmethod +/// def eat(sheep): +/// pass +/// ``` +/// +/// [PEP 8]: https://peps.python.org/pep-0008/#function-and-method-arguments +#[violation] +pub struct BadStaticmethodArgument { + argument_name: String, +} + +impl Violation for BadStaticmethodArgument { + #[derive_message_formats] + fn message(&self) -> String { + let Self { argument_name } = self; + format!("First argument of a static method should not be named `{argument_name}`") + } +} + +/// PLW0211 +pub(crate) fn bad_staticmethod_argument( + checker: &Checker, + scope: &Scope, + diagnostics: &mut Vec, +) { + let Some(func) = scope.kind.as_function() else { + return; + }; + + let ast::StmtFunctionDef { + name, + decorator_list, + parameters, + .. + } = func; + + let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else { + return; + }; + + let type_ = function_type::classify( + name, + decorator_list, + parent, + checker.semantic(), + &checker.settings.pep8_naming.classmethod_decorators, + &checker.settings.pep8_naming.staticmethod_decorators, + ); + if !matches!(type_, function_type::FunctionType::StaticMethod) { + return; + } + + let Some(ParameterWithDefault { + parameter: self_or_cls, + .. + }) = parameters + .posonlyargs + .first() + .or_else(|| parameters.args.first()) + else { + return; + }; + + if matches!(self_or_cls.name.as_str(), "self" | "cls") { + let diagnostic = Diagnostic::new( + BadStaticmethodArgument { + argument_name: self_or_cls.name.to_string(), + }, + self_or_cls.range(), + ); + diagnostics.push(diagnostic); + } +} diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index 99bbb5063efdb..93f89e1f6cec0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -3,6 +3,7 @@ pub(crate) use assert_on_string_literal::*; pub(crate) use await_outside_async::*; pub(crate) use bad_dunder_method_name::*; pub(crate) use bad_open_mode::*; +pub(crate) use bad_staticmethod_argument::*; pub(crate) use bad_str_strip_call::*; pub(crate) use bad_string_format_character::BadStringFormatCharacter; pub(crate) use bad_string_format_type::*; @@ -97,6 +98,7 @@ mod assert_on_string_literal; mod await_outside_async; mod bad_dunder_method_name; mod bad_open_mode; +mod bad_staticmethod_argument; mod bad_str_strip_call; pub(crate) mod bad_string_format_character; mod bad_string_format_type; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap new file mode 100644 index 0000000000000..add63e311b094 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0211_bad_staticmethod_argument.py.snap @@ -0,0 +1,28 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +--- +bad_staticmethod_argument.py:3:13: PLW0211 First argument of a static method should not be named `self` + | +1 | class Wolf: +2 | @staticmethod +3 | def eat(self): # [bad-staticmethod-argument] + | ^^^^ PLW0211 +4 | pass + | + +bad_staticmethod_argument.py:15:13: PLW0211 First argument of a static method should not be named `cls` + | +13 | class Sheep: +14 | @staticmethod +15 | def eat(cls, x, y, z): # [bad-staticmethod-argument] + | ^^^ PLW0211 +16 | pass + | + +bad_staticmethod_argument.py:19:15: PLW0211 First argument of a static method should not be named `self` + | +18 | @staticmethod +19 | def sleep(self, x, y, z): # [bad-staticmethod-argument] + | ^^^^ PLW0211 +20 | pass + | diff --git a/ruff.schema.json b/ruff.schema.json index f6d36e1cfc51a..4aa04519ad805 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3389,6 +3389,8 @@ "PLW0131", "PLW0133", "PLW02", + "PLW021", + "PLW0211", "PLW024", "PLW0245", "PLW04", From a4ee9c1978f3f554fdaf845535a0aedb82b0dd19 Mon Sep 17 00:00:00 2001 From: buhtz Date: Sat, 6 Apr 2024 00:12:33 +0200 Subject: [PATCH 47/54] doc(FAQ): More precise PyLint comparision (#10756) Section about comparing Ruff to PyLint now is more precise about the following two points: - Ruff do count branches different and there for earlier give too-many-branches warning. - Activating all Pylint rules in Ruff also activates pylint rules that are not active by default in Pylint itself because they are implemented via pylint plugins. --- docs/faq.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 6311dddfdccda..4509eb05287aa 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -126,8 +126,16 @@ Despite these differences, many users have successfully switched from Pylint to those using Ruff alongside a [type checker](faq.md#how-does-ruff-compare-to-mypy-or-pyright-or-pyre), which can cover some of the functionality that Pylint provides. -Like Flake8, Pylint supports plugins (called "checkers"), while Ruff implements all rules natively. -Unlike Pylint, Ruff is capable of automatically fixing its own lint violations. +Like Flake8, Pylint supports plugins (called "checkers"), while Ruff implements all rules natively +and does not support custom or third-party rules. Unlike Pylint, Ruff is capable of automatically +fixing its own lint violations. + +In some cases, Ruff's rules may yield slightly different rules than their Pylint counterparts. For +example, Ruff's [`too-many-branches`](rules/too-many-branches.md) does not count `try` blocks as +their own branches, unlike Pylint's `R0912`. Ruff's `PY` rule group also includes a small number of +rules from Pylint _extensions_ (like [`magic-value-comparison`](https://docs.astral.sh/ruff/rules/magic-value-comparison/)), +which need to be explicitly activated when using Pylint. By enabling Ruff's `PY` group, you may +see violations for rules that weren't previously enabled through your Pylint configuration. Pylint parity is being tracked in [#970](https://github.com/astral-sh/ruff/issues/970). From a184dc68f5414cb05ec409a7c887c33c7442c7e2 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Fri, 5 Apr 2024 15:41:50 -0700 Subject: [PATCH 48/54] Implement client setting initialization and resolution for `ruff server` (#10764) ## Summary When a language server initializes, it is passed a serialized JSON object, which is known as its "initialization options". Until now, `ruff server` has ignored those initialization options, meaning that user-provided settings haven't worked. This PR is the first step for supporting settings from the LSP client. It implements procedures to deserialize initialization options into a settings object, and then resolve those settings objects into concrete settings for each workspace. One of the goals for user settings implementation in `ruff server` is backwards compatibility with `ruff-lsp`'s settings. We won't support all settings that `ruff-lsp` had, but the ones that we do support should work the same and use the same schema as `ruff-lsp`. These are the existing settings from `ruff-lsp` that we will continue to support, and which are part of the settings schema in this PR: | Setting | Default Value | Description | |----------------------------------------|---------------|----------------------------------------------------------------------------------------| | `codeAction.disableRuleComment.enable` | `true` | Whether to display Quick Fix actions to disable rules via `noqa` suppression comments. | | `codeAction.fixViolation.enable` | `true` | Whether to display Quick Fix actions to autofix violations. | | `fixAll` | `true` | Whether to register Ruff as capable of handling `source.fixAll` actions. | | `lint.enable` | `true` | Whether to enable linting. Set to `false` to use Ruff exclusively as a formatter. | | `organizeImports` | `true` | Whether to register Ruff as capable of handling `source.organizeImports` actions. | To be clear: this PR does not implement 'support' for these settings, individually. Rather, it constructs a framework for these settings to be used by the server in the future. Notably, we are choosing *not* to support `lint.args` and `format.args` as settings for `ruff server`. This is because we're now interfacing with Ruff at a lower level than its CLI, and converting CLI arguments back into configuration is too involved. We will have support for linter and formatter specific settings in follow-up PRs. We will also 'hook up' user settings to work with the server in follow up PRs. ## Test Plan ### Snapshot Tests Tests have been created in `crates/ruff_server/src/session/settings/tests.rs` to ensure that deserialization and settings resolution works as expected. ### Manual Testing Since we aren't using the resolved settings anywhere yet, we'll have to add a few printing statements. We want to capture what the resolved settings look like when sent as part of a snapshot, so modify `Session::take_snapshot` to be the following: ```rust pub(crate) fn take_snapshot(&self, url: &Url) -> Option { let resolved_settings = self.workspaces.client_settings(url, &self.global_settings); tracing::info!("Resolved settings for document {url}: {resolved_settings:?}"); Some(DocumentSnapshot { configuration: self.workspaces.configuration(url)?.clone(), resolved_client_capabilities: self.resolved_client_capabilities.clone(), client_settings: resolved_settings, document_ref: self.workspaces.snapshot(url)?, position_encoding: self.position_encoding, url: url.clone(), }) } ``` Once you've done that, build the server and start up your extension testing environment. 1. Set up a workspace in VS Code with two workspace folders, each one having some variant of Ruff file-based configuration (`pyproject.toml`, `ruff.toml`, etc.). We'll call these folders `folder_a` and `folder_b`. 2. In each folder, open up `.vscode/settings.json`. 3. In folder A, use these settings: ```json { "ruff.codeAction.disableRuleComment": { "enable": true } } ``` 4. In folder B, use these settings: ```json { "ruff.codeAction.disableRuleComment": { "enable": false } } ``` 5. Finally, open up your VS Code User Settings and un-check the `Ruff > Code Action: Disable Rule Comment` setting. 6. When opening files in `folder_a`, you should see logs that look like this: ``` Resolved settings for document : ResolvedClientSettings { fix_all: true, organize_imports: true, lint_enable: true, disable_rule_comment_enable: true, fix_violation_enable: true } ``` 7. When opening files in `folder_b`, you should see logs that look like this: ``` Resolved settings for document : ResolvedClientSettings { fix_all: true, organize_imports: true, lint_enable: true, disable_rule_comment_enable: false, fix_violation_enable: true } ``` 8. To test invalid configuration, change `.vscode/settings.json` in either folder to be this: ```json { "ruff.codeAction.disableRuleComment": { "enable": "invalid" }, } ``` 10. You should now see these error logs: ```