Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7fb73500d | ||
|
|
7dabc4598b | ||
|
|
6a1fa4778f | ||
|
|
c3d6d5d006 | ||
|
|
9a8400a287 | ||
|
|
d71c65d0c8 | ||
|
|
7f92bfbc4a | ||
|
|
37301375c8 | ||
|
|
c07947bfac | ||
|
|
72964529a5 | ||
|
|
eab8ca4d7e | ||
|
|
5b3e922050 | ||
|
|
311a7751f9 | ||
|
|
5e2bb8ca07 | ||
|
|
3c8d9d45fb | ||
|
|
82c3c513d2 | ||
|
|
f2dc01e3aa | ||
|
|
5349143fca | ||
|
|
b6f23d57aa | ||
|
|
b7b6e0136e | ||
|
|
218f517487 | ||
|
|
75c669a007 | ||
|
|
2d5ce4532a | ||
|
|
f3e2d12609 | ||
|
|
de2d7e97b1 | ||
|
|
bcb737dd80 | ||
|
|
8c146bbf11 | ||
|
|
4170ef0508 | ||
|
|
72ebde8d38 | ||
|
|
1672a3d3b7 | ||
|
|
8c0d65c98e | ||
|
|
b3c2935fa5 | ||
|
|
e57bccd500 | ||
|
|
75c9be099f |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -184,7 +184,11 @@ jobs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
if: github.event_name == 'pull_request'
|
||||
# Ecosystem check needs linter and/or formatter changes.
|
||||
if: github.event_name == 'pull_request' && ${{
|
||||
needs.determine_changes.outputs.linter == 'true' ||
|
||||
needs.determine_changes.outputs.formatter == 'true'
|
||||
}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
|
||||
@@ -13,12 +13,12 @@ exclude: |
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.12.1
|
||||
rev: v0.15
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/executablebooks/mdformat
|
||||
rev: 0.7.16
|
||||
rev: 0.7.17
|
||||
hooks:
|
||||
- id: mdformat
|
||||
additional_dependencies:
|
||||
@@ -26,16 +26,22 @@ repos:
|
||||
- mdformat-admon
|
||||
exclude: |
|
||||
(?x)^(
|
||||
docs/formatter/black.md
|
||||
docs/formatter/black\.md
|
||||
| docs/\w+\.md
|
||||
)$
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.33.0
|
||||
rev: v0.37.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
exclude: |
|
||||
(?x)^(
|
||||
docs/formatter/black\.md
|
||||
| docs/\w+\.md
|
||||
)$
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.14.12
|
||||
rev: v1.16.22
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -49,7 +55,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.3
|
||||
rev: v0.1.4
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -64,7 +70,7 @@ repos:
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0
|
||||
rev: v3.0.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
|
||||
@@ -72,7 +72,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
charlie.r.marsh@gmail.com.
|
||||
<charlie.r.marsh@gmail.com>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
|
||||
74
Cargo.lock
generated
74
Cargo.lock
generated
@@ -210,9 +210,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
@@ -383,7 +383,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -407,9 +407,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d680ccd1eedd2dd7c7a3649a78c7d06e0f16b191b30d81cc58e7bc906488d344"
|
||||
checksum = "918b13a0f1a32460ab3bd5debd56b5a27a7071fa5ff5dfeb3a5cf291a85b174b"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"libc",
|
||||
@@ -418,9 +418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58b48b6c8e890d7d4ad0ed85e9ab4949bf7023198c006000ef6338ba84cf5b71"
|
||||
checksum = "c683c7fef2b873fbbdf4062782914c652309951244bf0bd362fe608b7d6f901c"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"colored",
|
||||
@@ -608,7 +608,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -619,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1129,7 +1129,7 @@ dependencies = [
|
||||
"pmutil 0.6.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1440,7 +1440,7 @@ version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
@@ -1707,7 +1707,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2067,7 +2067,7 @@ dependencies = [
|
||||
"argfile",
|
||||
"assert_cmd",
|
||||
"bincode",
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"cachedir",
|
||||
"chrono",
|
||||
"clap",
|
||||
@@ -2201,7 +2201,7 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
@@ -2267,7 +2267,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2293,7 +2293,7 @@ dependencies = [
|
||||
name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
@@ -2325,7 +2325,7 @@ name = "ruff_python_formatter"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"clap",
|
||||
"countme",
|
||||
"insta",
|
||||
@@ -2369,7 +2369,7 @@ dependencies = [
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"hexf-parse",
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
@@ -2383,7 +2383,7 @@ name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"itertools 0.11.0",
|
||||
@@ -2413,7 +2413,7 @@ dependencies = [
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"is-macro",
|
||||
"ruff_index",
|
||||
"ruff_python_ast",
|
||||
@@ -2563,7 +2563,7 @@ version = "0.38.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -2682,9 +2682,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
|
||||
checksum = "17ba92964781421b6cef36bf0d7da26d201e96d84e1b10e7ae6ed416e516906d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
@@ -2699,7 +2699,7 @@ checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2715,9 +2715,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.107"
|
||||
version = "1.0.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2761,7 +2761,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2856,7 +2856,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2872,9 +2872,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.38"
|
||||
version = "2.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2961,7 +2961,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2973,7 +2973,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -2994,7 +2994,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3131,7 +3131,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3349,7 +3349,7 @@ checksum = "3d8c6bba9b149ee82950daefc9623b32bb1dacbfb1890e352f6b887bd582adaf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3443,7 +3443,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3477,7 +3477,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -13,7 +13,7 @@ license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
bitflags = { version = "2.4.1" }
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
@@ -35,14 +35,14 @@ regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.190", features = ["derive"] }
|
||||
serde_json = { version = "1.0.107" }
|
||||
serde_json = { version = "1.0.108" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.3.0", features = ["inline"] }
|
||||
smallvec = { version = "1.11.1" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
syn = { version = "2.0.38" }
|
||||
syn = { version = "2.0.39" }
|
||||
test-case = { version = "3.2.1" }
|
||||
thiserror = { version = "1.0.50" }
|
||||
toml = { version = "0.7.8" }
|
||||
@@ -56,7 +56,7 @@ uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev.package.insta]
|
||||
|
||||
@@ -415,6 +415,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [PDM](https://github.com/pdm-project/pdm)
|
||||
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
|
||||
- [Pandas](https://github.com/pandas-dev/pandas)
|
||||
- [Pillow](https://github.com/python-pillow/Pillow)
|
||||
- [Poetry](https://github.com/python-poetry/poetry)
|
||||
- [Polars](https://github.com/pola-rs/polars)
|
||||
- [PostHog](https://github.com/PostHog/posthog)
|
||||
@@ -423,6 +424,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [PyTorch](https://github.com/pytorch/pytorch)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Pylint](https://github.com/PyCQA/pylint)
|
||||
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
|
||||
- [Reflex](https://github.com/reflex-dev/reflex)
|
||||
- [Rippling](https://rippling.com)
|
||||
- [Robyn](https://github.com/sansyrox/robyn)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[files]
|
||||
extend-exclude = ["resources", "snapshots"]
|
||||
# https://github.com/crate-ci/typos/issues/868
|
||||
extend-exclude = ["**/resources/**/*", "**/snapshots/**/*"]
|
||||
|
||||
[default.extend-words]
|
||||
hel = "hel"
|
||||
|
||||
@@ -37,7 +37,7 @@ serde_json.workspace = true
|
||||
url = "2.3.1"
|
||||
ureq = "2.8.0"
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
codspeed-criterion-compat = { version="2.3.0", default-features = false, optional = true}
|
||||
codspeed-criterion-compat = { version="2.3.1", default-features = false, optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_linter.path = "../ruff_linter"
|
||||
|
||||
@@ -278,7 +278,7 @@ pub struct CheckCommand {
|
||||
#[arg(long, help_heading = "Rule configuration", hide = true)]
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long, help_heading = "Miscellaneous")]
|
||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||
pub no_cache: bool,
|
||||
/// Ignore all configuration files.
|
||||
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
|
||||
@@ -374,7 +374,7 @@ pub struct FormatCommand {
|
||||
pub config: Option<PathBuf>,
|
||||
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long, help_heading = "Miscellaneous")]
|
||||
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
|
||||
pub no_cache: bool,
|
||||
/// Path to the cache directory.
|
||||
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruff_python_trivia::textwrap;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
|
||||
@@ -125,22 +126,57 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push('\n');
|
||||
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
|
||||
output.push('\n');
|
||||
output.push_str(&format!(
|
||||
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
|
||||
if let Some(set_name) = parent_set.name() {
|
||||
if set_name == "format" {
|
||||
String::from(".format")
|
||||
} else {
|
||||
format!(".lint.{set_name}")
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
field.example
|
||||
output.push_str("**Example usage**:\n\n");
|
||||
output.push_str(&format_tab(
|
||||
"pyproject.toml",
|
||||
&format_header(parent_set, ConfigurationFile::PyprojectToml),
|
||||
field.example,
|
||||
));
|
||||
output.push_str(&format_tab(
|
||||
"ruff.toml",
|
||||
&format_header(parent_set, ConfigurationFile::RuffToml),
|
||||
field.example,
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
|
||||
format!(
|
||||
"=== \"{}\"\n\n ```toml\n {}\n{}\n ```\n",
|
||||
tab_name,
|
||||
header,
|
||||
textwrap::indent(content, " ")
|
||||
)
|
||||
}
|
||||
|
||||
fn format_header(parent_set: &Set, configuration: ConfigurationFile) -> String {
|
||||
let fmt = if let Some(set_name) = parent_set.name() {
|
||||
if set_name == "format" {
|
||||
String::from(".format")
|
||||
} else {
|
||||
format!(".lint.{set_name}")
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
match configuration {
|
||||
ConfigurationFile::PyprojectToml => format!("[tool.ruff{fmt}]"),
|
||||
ConfigurationFile::RuffToml => {
|
||||
if fmt.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("[{}]", fmt.strip_prefix('.').unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ConfigurationFile {
|
||||
PyprojectToml,
|
||||
RuffToml,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CollectOptionsVisitor {
|
||||
groups: Vec<(String, OptionSet)>,
|
||||
|
||||
@@ -107,6 +107,30 @@ impl Fix {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with the specified [`Applicability`] to apply an [`Edit`] element.
|
||||
pub fn applicable_edit(edit: Edit, applicability: Applicability) -> Self {
|
||||
Self {
|
||||
edits: vec![edit],
|
||||
applicability,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] with the specified [`Applicability`] to apply multiple [`Edit`] elements.
|
||||
pub fn applicable_edits(
|
||||
edit: Edit,
|
||||
rest: impl IntoIterator<Item = Edit>,
|
||||
applicability: Applicability,
|
||||
) -> Self {
|
||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||
edits.sort_by_key(|edit| (edit.start(), edit.end()));
|
||||
Self {
|
||||
edits,
|
||||
applicability,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`TextSize`] of the first [`Edit`] in the [`Fix`].
|
||||
pub fn min_start(&self) -> Option<TextSize> {
|
||||
self.edits.first().map(Edit::start)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
obj = {}
|
||||
|
||||
key in obj.keys() # SIM118
|
||||
|
||||
key not in obj.keys() # SIM118
|
||||
|
||||
64
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py
vendored
Normal file
64
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO105.py
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import trio
|
||||
|
||||
|
||||
async def func() -> None:
|
||||
trio.run(foo) # OK, not async
|
||||
|
||||
# OK
|
||||
await trio.aclose_forcefully(foo)
|
||||
await trio.open_file(foo)
|
||||
await trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
await trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
await trio.open_tcp_listeners(foo)
|
||||
await trio.open_tcp_stream(foo, foo)
|
||||
await trio.open_unix_socket(foo)
|
||||
await trio.run_process(foo)
|
||||
await trio.sleep(5)
|
||||
await trio.sleep_until(5)
|
||||
await trio.lowlevel.cancel_shielded_checkpoint()
|
||||
await trio.lowlevel.checkpoint()
|
||||
await trio.lowlevel.checkpoint_if_cancelled()
|
||||
await trio.lowlevel.open_process(foo)
|
||||
await trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
await trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
await trio.lowlevel.wait_readable(foo)
|
||||
await trio.lowlevel.wait_task_rescheduled(foo)
|
||||
await trio.lowlevel.wait_writable(foo)
|
||||
|
||||
# TRIO105
|
||||
trio.aclose_forcefully(foo)
|
||||
trio.open_file(foo)
|
||||
trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
trio.open_tcp_listeners(foo)
|
||||
trio.open_tcp_stream(foo, foo)
|
||||
trio.open_unix_socket(foo)
|
||||
trio.run_process(foo)
|
||||
trio.serve_listeners(foo, foo)
|
||||
trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
trio.serve_tcp(foo, foo)
|
||||
trio.sleep(foo)
|
||||
trio.sleep_forever()
|
||||
trio.sleep_until(foo)
|
||||
trio.lowlevel.cancel_shielded_checkpoint()
|
||||
trio.lowlevel.checkpoint()
|
||||
trio.lowlevel.checkpoint_if_cancelled()
|
||||
trio.lowlevel.open_process()
|
||||
trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
trio.lowlevel.wait_readable(foo)
|
||||
trio.lowlevel.wait_task_rescheduled(foo)
|
||||
trio.lowlevel.wait_writable(foo)
|
||||
|
||||
async with await trio.open_file(foo): # Ok
|
||||
pass
|
||||
|
||||
async with trio.open_file(foo): # TRIO105
|
||||
pass
|
||||
|
||||
|
||||
def func() -> None:
|
||||
# TRIO105 (without fix)
|
||||
trio.open_file(foo)
|
||||
28
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py
vendored
Normal file
28
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO115.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import trio
|
||||
from trio import sleep
|
||||
|
||||
|
||||
async def func():
|
||||
await trio.sleep(0) # TRIO115
|
||||
await trio.sleep(1) # OK
|
||||
await trio.sleep(0, 1) # OK
|
||||
await trio.sleep(...) # OK
|
||||
await trio.sleep() # OK
|
||||
|
||||
trio.sleep(0) # TRIO115
|
||||
foo = 0
|
||||
trio.sleep(foo) # TRIO115
|
||||
trio.sleep(1) # OK
|
||||
time.sleep(0) # OK
|
||||
|
||||
sleep(0) # TRIO115
|
||||
|
||||
bar = "bar"
|
||||
trio.sleep(bar)
|
||||
|
||||
|
||||
trio.sleep(0) # TRIO115
|
||||
|
||||
|
||||
def func():
|
||||
trio.run(trio.sleep(0)) # TRIO115
|
||||
@@ -69,3 +69,5 @@ while 1:
|
||||
#: E701:2:3
|
||||
a = \
|
||||
5;
|
||||
#:
|
||||
with x(y) as z: ...
|
||||
|
||||
@@ -4,6 +4,9 @@ if type(res) == type(42):
|
||||
#: E721
|
||||
if type(res) != type(""):
|
||||
pass
|
||||
#: E721
|
||||
if type(res) == memoryview:
|
||||
pass
|
||||
#: Okay
|
||||
import types
|
||||
|
||||
@@ -47,6 +50,14 @@ if isinstance(res, str):
|
||||
pass
|
||||
if isinstance(res, types.MethodType):
|
||||
pass
|
||||
if isinstance(res, memoryview):
|
||||
pass
|
||||
#: Okay
|
||||
if type(res) is type:
|
||||
pass
|
||||
#: E721
|
||||
if type(res) == type:
|
||||
pass
|
||||
#: Okay
|
||||
def func_histype(a, b, c):
|
||||
pass
|
||||
|
||||
@@ -31,3 +31,7 @@ def make_unique_pod_id(pod_id: str) -> str | None:
|
||||
:param pod_id: requested pod name
|
||||
:return: ``str`` valid Pod name of appropriate length
|
||||
"""
|
||||
|
||||
|
||||
def shouldnt_add_raw_here2():
|
||||
u"Sum\\mary."
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
def f(tup):
|
||||
x, y = tup # this does NOT trigger F841
|
||||
x, y = tup
|
||||
|
||||
|
||||
def f():
|
||||
@@ -7,17 +7,17 @@ def f():
|
||||
|
||||
|
||||
def f():
|
||||
(x, y) = coords = 1, 2 # this does NOT trigger F841
|
||||
(x, y) = coords = 1, 2
|
||||
if x > 1:
|
||||
print(coords)
|
||||
|
||||
|
||||
def f():
|
||||
(x, y) = coords = 1, 2 # this triggers F841 on coords
|
||||
(x, y) = coords = 1, 2
|
||||
|
||||
|
||||
def f():
|
||||
coords = (x, y) = 1, 2 # this triggers F841 on coords
|
||||
coords = (x, y) = 1, 2
|
||||
|
||||
|
||||
def f():
|
||||
|
||||
32
crates/ruff_linter/resources/test/fixtures/pyflakes/F841_4.py
vendored
Normal file
32
crates/ruff_linter/resources/test/fixtures/pyflakes/F841_4.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Test fix for issue #8441.
|
||||
|
||||
Ref: https://github.com/astral-sh/ruff/issues/8441
|
||||
"""
|
||||
|
||||
|
||||
def foo():
|
||||
...
|
||||
|
||||
|
||||
def bar():
|
||||
a = foo()
|
||||
b, c = foo()
|
||||
|
||||
|
||||
def baz():
|
||||
d, _e = foo()
|
||||
print(d)
|
||||
|
||||
|
||||
def qux():
|
||||
f, _ = foo()
|
||||
print(f)
|
||||
|
||||
|
||||
def quux():
|
||||
g, h = foo()
|
||||
print(g, h)
|
||||
|
||||
|
||||
def quuz():
|
||||
_i, _j = foo()
|
||||
@@ -43,6 +43,12 @@ def yes_four(x: Dict[int, str]):
|
||||
del x[:]
|
||||
|
||||
|
||||
def yes_five(x: Dict[int, str]):
|
||||
# FURB131
|
||||
del x[:]
|
||||
|
||||
x = 1
|
||||
|
||||
# these should not
|
||||
|
||||
del names["key"]
|
||||
|
||||
67
crates/ruff_linter/resources/test/fixtures/refurb/FURB169.py
vendored
Normal file
67
crates/ruff_linter/resources/test/fixtures/refurb/FURB169.py
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
foo = None
|
||||
|
||||
# Error.
|
||||
|
||||
type(foo) is type(None)
|
||||
|
||||
type(None) is type(foo)
|
||||
|
||||
type(None) is type(None)
|
||||
|
||||
type(foo) is not type(None)
|
||||
|
||||
type(None) is not type(foo)
|
||||
|
||||
type(None) is not type(None)
|
||||
|
||||
type(foo) == type(None)
|
||||
|
||||
type(None) == type(foo)
|
||||
|
||||
type(None) == type(None)
|
||||
|
||||
type(foo) != type(None)
|
||||
|
||||
type(None) != type(foo)
|
||||
|
||||
type(None) != type(None)
|
||||
|
||||
# Ok.
|
||||
|
||||
foo is None
|
||||
|
||||
foo is not None
|
||||
|
||||
None is foo
|
||||
|
||||
None is not foo
|
||||
|
||||
None is None
|
||||
|
||||
None is not None
|
||||
|
||||
foo is type(None)
|
||||
|
||||
type(foo) is None
|
||||
|
||||
type(None) is None
|
||||
|
||||
foo is not type(None)
|
||||
|
||||
type(foo) is not None
|
||||
|
||||
type(None) is not None
|
||||
|
||||
foo == type(None)
|
||||
|
||||
type(foo) == None
|
||||
|
||||
type(None) == None
|
||||
|
||||
foo != type(None)
|
||||
|
||||
type(foo) != None
|
||||
|
||||
type(None) != None
|
||||
|
||||
type(foo) > type(None)
|
||||
@@ -15,8 +15,8 @@ use crate::rules::{
|
||||
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
|
||||
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
|
||||
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_trio, flake8_use_pathlib, flynt, numpy,
|
||||
pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -926,6 +926,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ImplicitCwd) {
|
||||
refurb::rules::no_implicit_cwd(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::TrioSyncCall) {
|
||||
flake8_trio::rules::sync_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::TrioZeroSleepCall) {
|
||||
flake8_trio::rules::zero_sleep_call(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(
|
||||
dict @ ast::ExprDict {
|
||||
@@ -1235,6 +1241,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::TypeNoneComparison) {
|
||||
refurb::rules::type_none_comparison(checker, compare);
|
||||
}
|
||||
if checker.enabled(Rule::SingleItemMembershipTest) {
|
||||
refurb::rules::single_item_membership_test(checker, expr, left, ops, comparators);
|
||||
}
|
||||
|
||||
@@ -292,6 +292,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
|
||||
// flake8-trio
|
||||
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
|
||||
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
|
||||
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
|
||||
|
||||
// flake8-builtins
|
||||
(Flake8Builtins, "001") => (RuleGroup::Stable, rules::flake8_builtins::rules::BuiltinVariableShadowing),
|
||||
@@ -944,6 +946,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
|
||||
(Refurb, "168") => (RuleGroup::Preview, rules::refurb::rules::IsinstanceTypeNone),
|
||||
(Refurb, "169") => (RuleGroup::Preview, rules::refurb::rules::TypeNoneComparison),
|
||||
(Refurb, "171") => (RuleGroup::Preview, rules::refurb::rules::SingleItemMembershipTest),
|
||||
(Refurb, "177") => (RuleGroup::Preview, rules::refurb::rules::ImplicitCwd),
|
||||
|
||||
|
||||
@@ -163,11 +163,15 @@ mod tests {
|
||||
"# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(",
|
||||
&[]
|
||||
));
|
||||
assert!(comment_contains_code(
|
||||
assert!(comment_contains_code("# )", &[]));
|
||||
|
||||
// This used to return true, but our parser has gotten a bit better
|
||||
// at rejecting invalid Python syntax. And indeed, this is not valid
|
||||
// Python code.
|
||||
assert!(!comment_contains_code(
|
||||
"# app_label=\"core\", model=\"user\"",
|
||||
&[]
|
||||
));
|
||||
assert!(comment_contains_code("# )", &[]));
|
||||
|
||||
// TODO(charlie): This should be `true` under aggressive mode.
|
||||
assert!(!comment_contains_code("#def foo():", &[]));
|
||||
|
||||
@@ -27,6 +27,11 @@ use crate::checkers::ast::Checker;
|
||||
/// raise AssertionError
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as changing an `assert` to a
|
||||
/// `raise` will change the behavior of your program when running in
|
||||
/// optimized mode (`python -O`).
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `assert`](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
|
||||
#[violation]
|
||||
|
||||
@@ -50,6 +50,16 @@ use crate::checkers::ast::Checker;
|
||||
/// return arg
|
||||
/// ```
|
||||
///
|
||||
/// If the use of a singleton is intentional, assign the result call to a
|
||||
/// module-level variable, and use that variable in the default argument:
|
||||
/// ```python
|
||||
/// ERROR = ValueError("Hosts weren't successfully added")
|
||||
///
|
||||
///
|
||||
/// def add_host(error: Exception = ERROR) -> None:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `flake8-bugbear.extend-immutable-calls`
|
||||
#[violation]
|
||||
@@ -62,9 +72,9 @@ impl Violation for FunctionCallInDefaultArgument {
|
||||
fn message(&self) -> String {
|
||||
let FunctionCallInDefaultArgument { name } = self;
|
||||
if let Some(name) = name {
|
||||
format!("Do not perform function call `{name}` in argument defaults")
|
||||
format!("Do not perform function call `{name}` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
|
||||
} else {
|
||||
format!("Do not perform function call in argument defaults")
|
||||
format!("Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B006_B008.py:102:61: B008 Do not perform function call `range` in argument defaults
|
||||
B006_B008.py:102:61: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
101 | # N.B. we're also flagging the function call in the comprehension
|
||||
102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
|
||||
@@ -9,21 +9,21 @@ B006_B008.py:102:61: B008 Do not perform function call `range` in argument defau
|
||||
103 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:106:64: B008 Do not perform function call `range` in argument defaults
|
||||
B006_B008.py:106:64: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
|
||||
| ^^^^^^^^ B008
|
||||
107 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:110:60: B008 Do not perform function call `range` in argument defaults
|
||||
B006_B008.py:110:60: B008 Do not perform function call `range` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
|
||||
| ^^^^^^^^ B008
|
||||
111 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument defaults
|
||||
B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
124 | # B008
|
||||
125 | # Flag function calls as default args (including if they are part of a sub-expression)
|
||||
@@ -32,21 +32,21 @@ B006_B008.py:126:39: B008 Do not perform function call `time.time` in argument d
|
||||
127 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:130:12: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:130:12: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
130 | def f(when=dt.datetime.now() + dt.timedelta(days=7)):
|
||||
| ^^^^^^^^^^^^^^^^^ B008
|
||||
131 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:134:30: B008 Do not perform function call in argument defaults
|
||||
B006_B008.py:134:30: B008 Do not perform function call in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
134 | def can_even_catch_lambdas(a=(lambda x: x)()):
|
||||
| ^^^^^^^^^^^^^^^ B008
|
||||
135 | ...
|
||||
|
|
||||
|
||||
B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
@@ -55,7 +55,7 @@ B006_B008.py:239:31: B008 Do not perform function call `dt.datetime.now` in argu
|
||||
240 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:245:22: B008 Do not perform function call `map` in argument defaults
|
||||
B006_B008.py:245:22: B008 Do not perform function call `map` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
243 | # Don't flag nested B006 since we can't guarantee that
|
||||
244 | # it isn't made mutable by the outer operation.
|
||||
@@ -64,7 +64,7 @@ B006_B008.py:245:22: B008 Do not perform function call `map` in argument default
|
||||
246 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argument defaults
|
||||
B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
249 | # B008-ception.
|
||||
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
@@ -72,7 +72,7 @@ B006_B008.py:250:19: B008 Do not perform function call `random.randint` in argum
|
||||
251 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:250:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
||||
B006_B008.py:250:37: B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
249 | # B008-ception.
|
||||
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B008_extended.py:24:51: B008 Do not perform function call `Depends` in argument defaults
|
||||
B008_extended.py:24:51: B008 Do not perform function call `Depends` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
|
|
||||
24 | def error_due_to_missing_import(data: List[str] = Depends(None)):
|
||||
| ^^^^^^^^^^^^^ B008
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -83,13 +83,14 @@ pub(crate) fn unnecessary_call_around_sorted(
|
||||
expr.range(),
|
||||
);
|
||||
diagnostic.try_set_fix(|| {
|
||||
let edit =
|
||||
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?;
|
||||
if outer.id == "reversed" {
|
||||
Ok(Fix::unsafe_edit(edit))
|
||||
} else {
|
||||
Ok(Fix::safe_edit(edit))
|
||||
}
|
||||
Ok(Fix::applicable_edit(
|
||||
fixes::fix_unnecessary_call_around_sorted(expr, checker.locator(), checker.stylist())?,
|
||||
if outer.id == "reversed" {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
))
|
||||
});
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::BindingKind;
|
||||
@@ -103,17 +103,23 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
|
||||
.next()
|
||||
.is_some_and(char::is_alphanumeric)
|
||||
{
|
||||
diagnostic.set_fix(if exception_type.is_some() {
|
||||
Fix::safe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
|
||||
} else {
|
||||
Fix::unsafe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
|
||||
});
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::range_replacement(" ".to_string(), arguments.range()),
|
||||
if exception_type.is_some() {
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
},
|
||||
));
|
||||
} else {
|
||||
diagnostic.set_fix(if exception_type.is_some() {
|
||||
Fix::safe_edit(Edit::range_deletion(arguments.range()))
|
||||
} else {
|
||||
Fix::unsafe_edit(Edit::range_deletion(arguments.range()))
|
||||
});
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::range_deletion(arguments.range()),
|
||||
if exception_type.is_some() {
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -55,6 +55,7 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::InDictKeys, Path::new("SIM118.py"))]
|
||||
#[test_case(Rule::IfElseBlockInsteadOfDictGet, Path::new("SIM401.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{Applicability, Edit};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Arguments, CmpOp, Comprehension, Expr};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -27,8 +28,19 @@ use crate::checkers::ast::Checker;
|
||||
/// key in foo
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// Given `key in obj.keys()`, `obj` _could_ be a dictionary, or it could be
|
||||
/// another type that defines a `.keys()` method. In the latter case, removing
|
||||
/// the `.keys()` attribute could lead to a runtime error.
|
||||
///
|
||||
/// As such, this rule's fixes are marked as unsafe. In [preview], though,
|
||||
/// fixes are marked as safe when Ruff can determine that `obj` is a
|
||||
/// dictionary.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Mapping Types](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict)
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[violation]
|
||||
pub struct InDictKeys {
|
||||
operator: String,
|
||||
@@ -113,6 +125,28 @@ fn key_in_dict(
|
||||
.skip_trivia()
|
||||
.find(|token| token.kind == SimpleTokenKind::Dot)
|
||||
{
|
||||
// The fix is only safe if we know the expression is a dictionary, since other types
|
||||
// can define a `.keys()` method.
|
||||
let applicability = if checker.settings.preview.is_enabled() {
|
||||
let is_dict = value.as_name_expr().is_some_and(|name| {
|
||||
let Some(binding) = checker
|
||||
.semantic()
|
||||
.only_binding(name)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
typing::is_dict(binding, checker.semantic())
|
||||
});
|
||||
if is_dict {
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
}
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
};
|
||||
|
||||
// If the `.keys()` is followed by (e.g.) a keyword, we need to insert a space,
|
||||
// since we're removing parentheses, which could lead to invalid syntax, as in:
|
||||
// ```python
|
||||
@@ -126,12 +160,15 @@ fn key_in_dict(
|
||||
.next()
|
||||
.is_some_and(|char| char.is_ascii_alphabetic())
|
||||
{
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
" ".to_string(),
|
||||
range,
|
||||
)));
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::range_replacement(" ".to_string(), range),
|
||||
applicability,
|
||||
));
|
||||
} else {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(range)));
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::range_deletion(range),
|
||||
applicability,
|
||||
));
|
||||
}
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -1,396 +1,401 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM118.py:1:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:3:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
1 | key in obj.keys() # SIM118
|
||||
1 | obj = {}
|
||||
2 |
|
||||
3 | key in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
2 |
|
||||
3 | key not in obj.keys() # SIM118
|
||||
4 |
|
||||
5 | key not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 |-key in obj.keys() # SIM118
|
||||
1 |+key in obj # SIM118
|
||||
1 1 | obj = {}
|
||||
2 2 |
|
||||
3 3 | key not in obj.keys() # SIM118
|
||||
3 |-key in obj.keys() # SIM118
|
||||
3 |+key in obj # SIM118
|
||||
4 4 |
|
||||
5 5 | key not in obj.keys() # SIM118
|
||||
6 6 |
|
||||
|
||||
SIM118.py:3:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
SIM118.py:5:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
1 | key in obj.keys() # SIM118
|
||||
2 |
|
||||
3 | key not in obj.keys() # SIM118
|
||||
3 | key in obj.keys() # SIM118
|
||||
4 |
|
||||
5 | key not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
4 |
|
||||
5 | foo["bar"] in obj.keys() # SIM118
|
||||
6 |
|
||||
7 | foo["bar"] in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | key in obj.keys() # SIM118
|
||||
2 2 |
|
||||
3 |-key not in obj.keys() # SIM118
|
||||
3 |+key not in obj # SIM118
|
||||
3 3 | key in obj.keys() # SIM118
|
||||
4 4 |
|
||||
5 5 | foo["bar"] in obj.keys() # SIM118
|
||||
5 |-key not in obj.keys() # SIM118
|
||||
5 |+key not in obj # SIM118
|
||||
6 6 |
|
||||
7 7 | foo["bar"] in obj.keys() # SIM118
|
||||
8 8 |
|
||||
|
||||
SIM118.py:5:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:7:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
3 | key not in obj.keys() # SIM118
|
||||
4 |
|
||||
5 | foo["bar"] in obj.keys() # SIM118
|
||||
5 | key not in obj.keys() # SIM118
|
||||
6 |
|
||||
7 | foo["bar"] in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
6 |
|
||||
7 | foo["bar"] not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
2 2 |
|
||||
3 3 | key not in obj.keys() # SIM118
|
||||
4 4 |
|
||||
5 |-foo["bar"] in obj.keys() # SIM118
|
||||
5 |+foo["bar"] in obj # SIM118
|
||||
6 6 |
|
||||
7 7 | foo["bar"] not in obj.keys() # SIM118
|
||||
8 8 |
|
||||
|
||||
SIM118.py:7:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
5 | foo["bar"] in obj.keys() # SIM118
|
||||
6 |
|
||||
7 | foo["bar"] not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
8 |
|
||||
9 | foo['bar'] in obj.keys() # SIM118
|
||||
9 | foo["bar"] not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
4 4 |
|
||||
5 5 | foo["bar"] in obj.keys() # SIM118
|
||||
5 5 | key not in obj.keys() # SIM118
|
||||
6 6 |
|
||||
7 |-foo["bar"] not in obj.keys() # SIM118
|
||||
7 |+foo["bar"] not in obj # SIM118
|
||||
7 |-foo["bar"] in obj.keys() # SIM118
|
||||
7 |+foo["bar"] in obj # SIM118
|
||||
8 8 |
|
||||
9 9 | foo['bar'] in obj.keys() # SIM118
|
||||
9 9 | foo["bar"] not in obj.keys() # SIM118
|
||||
10 10 |
|
||||
|
||||
SIM118.py:9:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:9:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
7 | foo["bar"] not in obj.keys() # SIM118
|
||||
7 | foo["bar"] in obj.keys() # SIM118
|
||||
8 |
|
||||
9 | foo['bar'] in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
9 | foo["bar"] not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
10 |
|
||||
11 | foo['bar'] not in obj.keys() # SIM118
|
||||
11 | foo['bar'] in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
6 6 |
|
||||
7 7 | foo["bar"] not in obj.keys() # SIM118
|
||||
7 7 | foo["bar"] in obj.keys() # SIM118
|
||||
8 8 |
|
||||
9 |-foo['bar'] in obj.keys() # SIM118
|
||||
9 |+foo['bar'] in obj # SIM118
|
||||
9 |-foo["bar"] not in obj.keys() # SIM118
|
||||
9 |+foo["bar"] not in obj # SIM118
|
||||
10 10 |
|
||||
11 11 | foo['bar'] not in obj.keys() # SIM118
|
||||
11 11 | foo['bar'] in obj.keys() # SIM118
|
||||
12 12 |
|
||||
|
||||
SIM118.py:11:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
SIM118.py:11:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
9 | foo['bar'] in obj.keys() # SIM118
|
||||
9 | foo["bar"] not in obj.keys() # SIM118
|
||||
10 |
|
||||
11 | foo['bar'] not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
11 | foo['bar'] in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
12 |
|
||||
13 | foo() in obj.keys() # SIM118
|
||||
13 | foo['bar'] not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
8 8 |
|
||||
9 9 | foo['bar'] in obj.keys() # SIM118
|
||||
9 9 | foo["bar"] not in obj.keys() # SIM118
|
||||
10 10 |
|
||||
11 |-foo['bar'] not in obj.keys() # SIM118
|
||||
11 |+foo['bar'] not in obj # SIM118
|
||||
11 |-foo['bar'] in obj.keys() # SIM118
|
||||
11 |+foo['bar'] in obj # SIM118
|
||||
12 12 |
|
||||
13 13 | foo() in obj.keys() # SIM118
|
||||
13 13 | foo['bar'] not in obj.keys() # SIM118
|
||||
14 14 |
|
||||
|
||||
SIM118.py:13:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:13:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
11 | foo['bar'] not in obj.keys() # SIM118
|
||||
11 | foo['bar'] in obj.keys() # SIM118
|
||||
12 |
|
||||
13 | foo() in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
13 | foo['bar'] not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
14 |
|
||||
15 | foo() not in obj.keys() # SIM118
|
||||
15 | foo() in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
10 10 |
|
||||
11 11 | foo['bar'] not in obj.keys() # SIM118
|
||||
11 11 | foo['bar'] in obj.keys() # SIM118
|
||||
12 12 |
|
||||
13 |-foo() in obj.keys() # SIM118
|
||||
13 |+foo() in obj # SIM118
|
||||
13 |-foo['bar'] not in obj.keys() # SIM118
|
||||
13 |+foo['bar'] not in obj # SIM118
|
||||
14 14 |
|
||||
15 15 | foo() not in obj.keys() # SIM118
|
||||
15 15 | foo() in obj.keys() # SIM118
|
||||
16 16 |
|
||||
|
||||
SIM118.py:15:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
SIM118.py:15:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
13 | foo() in obj.keys() # SIM118
|
||||
13 | foo['bar'] not in obj.keys() # SIM118
|
||||
14 |
|
||||
15 | foo() not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
15 | foo() in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
16 |
|
||||
17 | for key in obj.keys(): # SIM118
|
||||
17 | foo() not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
12 12 |
|
||||
13 13 | foo() in obj.keys() # SIM118
|
||||
13 13 | foo['bar'] not in obj.keys() # SIM118
|
||||
14 14 |
|
||||
15 |-foo() not in obj.keys() # SIM118
|
||||
15 |+foo() not in obj # SIM118
|
||||
15 |-foo() in obj.keys() # SIM118
|
||||
15 |+foo() in obj # SIM118
|
||||
16 16 |
|
||||
17 17 | for key in obj.keys(): # SIM118
|
||||
18 18 | pass
|
||||
17 17 | foo() not in obj.keys() # SIM118
|
||||
18 18 |
|
||||
|
||||
SIM118.py:17:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:17:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
15 | foo() not in obj.keys() # SIM118
|
||||
15 | foo() in obj.keys() # SIM118
|
||||
16 |
|
||||
17 | for key in obj.keys(): # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
18 | pass
|
||||
17 | foo() not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
18 |
|
||||
19 | for key in obj.keys(): # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
14 14 |
|
||||
15 15 | foo() not in obj.keys() # SIM118
|
||||
15 15 | foo() in obj.keys() # SIM118
|
||||
16 16 |
|
||||
17 |-for key in obj.keys(): # SIM118
|
||||
17 |+for key in obj: # SIM118
|
||||
18 18 | pass
|
||||
19 19 |
|
||||
20 20 | for key in list(obj.keys()):
|
||||
17 |-foo() not in obj.keys() # SIM118
|
||||
17 |+foo() not in obj # SIM118
|
||||
18 18 |
|
||||
19 19 | for key in obj.keys(): # SIM118
|
||||
20 20 | pass
|
||||
|
||||
SIM118.py:24:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:19:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
22 | del obj[key]
|
||||
23 |
|
||||
24 | [k for k in obj.keys()] # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
25 |
|
||||
26 | {k for k in obj.keys()} # SIM118
|
||||
17 | foo() not in obj.keys() # SIM118
|
||||
18 |
|
||||
19 | for key in obj.keys(): # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
20 | pass
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
21 21 | if some_property(key):
|
||||
22 22 | del obj[key]
|
||||
23 23 |
|
||||
24 |-[k for k in obj.keys()] # SIM118
|
||||
24 |+[k for k in obj] # SIM118
|
||||
25 25 |
|
||||
26 26 | {k for k in obj.keys()} # SIM118
|
||||
27 27 |
|
||||
16 16 |
|
||||
17 17 | foo() not in obj.keys() # SIM118
|
||||
18 18 |
|
||||
19 |-for key in obj.keys(): # SIM118
|
||||
19 |+for key in obj: # SIM118
|
||||
20 20 | pass
|
||||
21 21 |
|
||||
22 22 | for key in list(obj.keys()):
|
||||
|
||||
SIM118.py:26:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
24 | [k for k in obj.keys()] # SIM118
|
||||
24 | del obj[key]
|
||||
25 |
|
||||
26 | {k for k in obj.keys()} # SIM118
|
||||
26 | [k for k in obj.keys()] # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
27 |
|
||||
28 | {k: k for k in obj.keys()} # SIM118
|
||||
28 | {k for k in obj.keys()} # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
23 23 |
|
||||
24 24 | [k for k in obj.keys()] # SIM118
|
||||
23 23 | if some_property(key):
|
||||
24 24 | del obj[key]
|
||||
25 25 |
|
||||
26 |-{k for k in obj.keys()} # SIM118
|
||||
26 |+{k for k in obj} # SIM118
|
||||
26 |-[k for k in obj.keys()] # SIM118
|
||||
26 |+[k for k in obj] # SIM118
|
||||
27 27 |
|
||||
28 28 | {k: k for k in obj.keys()} # SIM118
|
||||
28 28 | {k for k in obj.keys()} # SIM118
|
||||
29 29 |
|
||||
|
||||
SIM118.py:28:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:28:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
26 | {k for k in obj.keys()} # SIM118
|
||||
26 | [k for k in obj.keys()] # SIM118
|
||||
27 |
|
||||
28 | {k: k for k in obj.keys()} # SIM118
|
||||
28 | {k for k in obj.keys()} # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
29 |
|
||||
30 | {k: k for k in obj.keys()} # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
25 25 |
|
||||
26 26 | [k for k in obj.keys()] # SIM118
|
||||
27 27 |
|
||||
28 |-{k for k in obj.keys()} # SIM118
|
||||
28 |+{k for k in obj} # SIM118
|
||||
29 29 |
|
||||
30 30 | {k: k for k in obj.keys()} # SIM118
|
||||
31 31 |
|
||||
|
||||
SIM118.py:30:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
28 | {k for k in obj.keys()} # SIM118
|
||||
29 |
|
||||
30 | {k: k for k in obj.keys()} # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
29 |
|
||||
30 | (k for k in obj.keys()) # SIM118
|
||||
31 |
|
||||
32 | (k for k in obj.keys()) # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
25 25 |
|
||||
26 26 | {k for k in obj.keys()} # SIM118
|
||||
27 27 |
|
||||
28 |-{k: k for k in obj.keys()} # SIM118
|
||||
28 |+{k: k for k in obj} # SIM118
|
||||
28 28 | {k for k in obj.keys()} # SIM118
|
||||
29 29 |
|
||||
30 30 | (k for k in obj.keys()) # SIM118
|
||||
30 |-{k: k for k in obj.keys()} # SIM118
|
||||
30 |+{k: k for k in obj} # SIM118
|
||||
31 31 |
|
||||
32 32 | (k for k in obj.keys()) # SIM118
|
||||
33 33 |
|
||||
|
||||
SIM118.py:30:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:32:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
28 | {k: k for k in obj.keys()} # SIM118
|
||||
29 |
|
||||
30 | (k for k in obj.keys()) # SIM118
|
||||
30 | {k: k for k in obj.keys()} # SIM118
|
||||
31 |
|
||||
32 | (k for k in obj.keys()) # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
31 |
|
||||
32 | key in (obj or {}).keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
27 27 |
|
||||
28 28 | {k: k for k in obj.keys()} # SIM118
|
||||
29 29 |
|
||||
30 |-(k for k in obj.keys()) # SIM118
|
||||
30 |+(k for k in obj) # SIM118
|
||||
31 31 |
|
||||
32 32 | key in (obj or {}).keys() # SIM118
|
||||
33 33 |
|
||||
|
||||
SIM118.py:32:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
30 | (k for k in obj.keys()) # SIM118
|
||||
31 |
|
||||
32 | key in (obj or {}).keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
33 |
|
||||
34 | (key) in (obj or {}).keys() # SIM118
|
||||
34 | key in (obj or {}).keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
29 29 |
|
||||
30 30 | (k for k in obj.keys()) # SIM118
|
||||
30 30 | {k: k for k in obj.keys()} # SIM118
|
||||
31 31 |
|
||||
32 |-key in (obj or {}).keys() # SIM118
|
||||
32 |+key in (obj or {}) # SIM118
|
||||
32 |-(k for k in obj.keys()) # SIM118
|
||||
32 |+(k for k in obj) # SIM118
|
||||
33 33 |
|
||||
34 34 | (key) in (obj or {}).keys() # SIM118
|
||||
34 34 | key in (obj or {}).keys() # SIM118
|
||||
35 35 |
|
||||
|
||||
SIM118.py:34:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
32 | key in (obj or {}).keys() # SIM118
|
||||
32 | (k for k in obj.keys()) # SIM118
|
||||
33 |
|
||||
34 | (key) in (obj or {}).keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
34 | key in (obj or {}).keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
35 |
|
||||
36 | from typing import KeysView
|
||||
36 | (key) in (obj or {}).keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
31 31 |
|
||||
32 32 | key in (obj or {}).keys() # SIM118
|
||||
32 32 | (k for k in obj.keys()) # SIM118
|
||||
33 33 |
|
||||
34 |-(key) in (obj or {}).keys() # SIM118
|
||||
34 |+(key) in (obj or {}) # SIM118
|
||||
34 |-key in (obj or {}).keys() # SIM118
|
||||
34 |+key in (obj or {}) # SIM118
|
||||
35 35 |
|
||||
36 36 | from typing import KeysView
|
||||
36 36 | (key) in (obj or {}).keys() # SIM118
|
||||
37 37 |
|
||||
|
||||
SIM118.py:48:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:36:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
48 | key in obj.keys()and foo
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
49 | (key in obj.keys())and foo
|
||||
50 | key in (obj.keys())and foo
|
||||
34 | key in (obj or {}).keys() # SIM118
|
||||
35 |
|
||||
36 | (key) in (obj or {}).keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
37 |
|
||||
38 | from typing import KeysView
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
45 45 |
|
||||
46 46 |
|
||||
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
48 |-key in obj.keys()and foo
|
||||
48 |+key in obj and foo
|
||||
49 49 | (key in obj.keys())and foo
|
||||
50 50 | key in (obj.keys())and foo
|
||||
51 51 |
|
||||
|
||||
SIM118.py:49:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
48 | key in obj.keys()and foo
|
||||
49 | (key in obj.keys())and foo
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
50 | key in (obj.keys())and foo
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
46 46 |
|
||||
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
48 48 | key in obj.keys()and foo
|
||||
49 |-(key in obj.keys())and foo
|
||||
49 |+(key in obj)and foo
|
||||
50 50 | key in (obj.keys())and foo
|
||||
51 51 |
|
||||
52 52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
33 33 |
|
||||
34 34 | key in (obj or {}).keys() # SIM118
|
||||
35 35 |
|
||||
36 |-(key) in (obj or {}).keys() # SIM118
|
||||
36 |+(key) in (obj or {}) # SIM118
|
||||
37 37 |
|
||||
38 38 | from typing import KeysView
|
||||
39 39 |
|
||||
|
||||
SIM118.py:50:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
48 | key in obj.keys()and foo
|
||||
49 | (key in obj.keys())and foo
|
||||
50 | key in (obj.keys())and foo
|
||||
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 | key in obj.keys()and foo
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
51 | (key in obj.keys())and foo
|
||||
52 | key in (obj.keys())and foo
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
47 47 |
|
||||
48 48 |
|
||||
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 |-key in obj.keys()and foo
|
||||
50 |+key in obj and foo
|
||||
51 51 | (key in obj.keys())and foo
|
||||
52 52 | key in (obj.keys())and foo
|
||||
53 53 |
|
||||
|
||||
SIM118.py:51:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 | key in obj.keys()and foo
|
||||
51 | (key in obj.keys())and foo
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
52 | key in (obj.keys())and foo
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
48 48 |
|
||||
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 50 | key in obj.keys()and foo
|
||||
51 |-(key in obj.keys())and foo
|
||||
51 |+(key in obj)and foo
|
||||
52 52 | key in (obj.keys())and foo
|
||||
53 53 |
|
||||
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
|
||||
SIM118.py:52:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
50 | key in obj.keys()and foo
|
||||
51 | (key in obj.keys())and foo
|
||||
52 | key in (obj.keys())and foo
|
||||
| ^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
51 |
|
||||
52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
53 |
|
||||
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
47 47 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
48 48 | key in obj.keys()and foo
|
||||
49 49 | (key in obj.keys())and foo
|
||||
50 |-key in (obj.keys())and foo
|
||||
50 |+key in (obj)and foo
|
||||
51 51 |
|
||||
52 52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
53 53 | for key in (
|
||||
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 50 | key in obj.keys()and foo
|
||||
51 51 | (key in obj.keys())and foo
|
||||
52 |-key in (obj.keys())and foo
|
||||
52 |+key in (obj)and foo
|
||||
53 53 |
|
||||
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
55 55 | for key in (
|
||||
|
||||
SIM118.py:53:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
SIM118.py:55:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
52 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
53 | for key in (
|
||||
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
55 | for key in (
|
||||
| _____^
|
||||
54 | | self.experiment.surveys[0]
|
||||
55 | | .stations[0]
|
||||
56 | | .keys()
|
||||
57 | | ):
|
||||
56 | | self.experiment.surveys[0]
|
||||
57 | | .stations[0]
|
||||
58 | | .keys()
|
||||
59 | | ):
|
||||
| |_^ SIM118
|
||||
58 | continue
|
||||
60 | continue
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
53 53 | for key in (
|
||||
54 54 | self.experiment.surveys[0]
|
||||
55 55 | .stations[0]
|
||||
56 |- .keys()
|
||||
56 |+
|
||||
57 57 | ):
|
||||
58 58 | continue
|
||||
55 55 | for key in (
|
||||
56 56 | self.experiment.surveys[0]
|
||||
57 57 | .stations[0]
|
||||
58 |- .keys()
|
||||
58 |+
|
||||
59 59 | ):
|
||||
60 60 | continue
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM118.py:3:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
1 | obj = {}
|
||||
2 |
|
||||
3 | key in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
4 |
|
||||
5 | key not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | obj = {}
|
||||
2 2 |
|
||||
3 |-key in obj.keys() # SIM118
|
||||
3 |+key in obj # SIM118
|
||||
4 4 |
|
||||
5 5 | key not in obj.keys() # SIM118
|
||||
6 6 |
|
||||
|
||||
SIM118.py:5:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
3 | key in obj.keys() # SIM118
|
||||
4 |
|
||||
5 | key not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
6 |
|
||||
7 | foo["bar"] in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
2 2 |
|
||||
3 3 | key in obj.keys() # SIM118
|
||||
4 4 |
|
||||
5 |-key not in obj.keys() # SIM118
|
||||
5 |+key not in obj # SIM118
|
||||
6 6 |
|
||||
7 7 | foo["bar"] in obj.keys() # SIM118
|
||||
8 8 |
|
||||
|
||||
SIM118.py:7:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
5 | key not in obj.keys() # SIM118
|
||||
6 |
|
||||
7 | foo["bar"] in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
8 |
|
||||
9 | foo["bar"] not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
4 4 |
|
||||
5 5 | key not in obj.keys() # SIM118
|
||||
6 6 |
|
||||
7 |-foo["bar"] in obj.keys() # SIM118
|
||||
7 |+foo["bar"] in obj # SIM118
|
||||
8 8 |
|
||||
9 9 | foo["bar"] not in obj.keys() # SIM118
|
||||
10 10 |
|
||||
|
||||
SIM118.py:9:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
7 | foo["bar"] in obj.keys() # SIM118
|
||||
8 |
|
||||
9 | foo["bar"] not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
10 |
|
||||
11 | foo['bar'] in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
6 6 |
|
||||
7 7 | foo["bar"] in obj.keys() # SIM118
|
||||
8 8 |
|
||||
9 |-foo["bar"] not in obj.keys() # SIM118
|
||||
9 |+foo["bar"] not in obj # SIM118
|
||||
10 10 |
|
||||
11 11 | foo['bar'] in obj.keys() # SIM118
|
||||
12 12 |
|
||||
|
||||
SIM118.py:11:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
9 | foo["bar"] not in obj.keys() # SIM118
|
||||
10 |
|
||||
11 | foo['bar'] in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
12 |
|
||||
13 | foo['bar'] not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
9 9 | foo["bar"] not in obj.keys() # SIM118
|
||||
10 10 |
|
||||
11 |-foo['bar'] in obj.keys() # SIM118
|
||||
11 |+foo['bar'] in obj # SIM118
|
||||
12 12 |
|
||||
13 13 | foo['bar'] not in obj.keys() # SIM118
|
||||
14 14 |
|
||||
|
||||
SIM118.py:13:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
11 | foo['bar'] in obj.keys() # SIM118
|
||||
12 |
|
||||
13 | foo['bar'] not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
14 |
|
||||
15 | foo() in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
10 10 |
|
||||
11 11 | foo['bar'] in obj.keys() # SIM118
|
||||
12 12 |
|
||||
13 |-foo['bar'] not in obj.keys() # SIM118
|
||||
13 |+foo['bar'] not in obj # SIM118
|
||||
14 14 |
|
||||
15 15 | foo() in obj.keys() # SIM118
|
||||
16 16 |
|
||||
|
||||
SIM118.py:15:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
13 | foo['bar'] not in obj.keys() # SIM118
|
||||
14 |
|
||||
15 | foo() in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
16 |
|
||||
17 | foo() not in obj.keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
12 12 |
|
||||
13 13 | foo['bar'] not in obj.keys() # SIM118
|
||||
14 14 |
|
||||
15 |-foo() in obj.keys() # SIM118
|
||||
15 |+foo() in obj # SIM118
|
||||
16 16 |
|
||||
17 17 | foo() not in obj.keys() # SIM118
|
||||
18 18 |
|
||||
|
||||
SIM118.py:17:1: SIM118 [*] Use `key not in dict` instead of `key not in dict.keys()`
|
||||
|
|
||||
15 | foo() in obj.keys() # SIM118
|
||||
16 |
|
||||
17 | foo() not in obj.keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
18 |
|
||||
19 | for key in obj.keys(): # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
14 14 |
|
||||
15 15 | foo() in obj.keys() # SIM118
|
||||
16 16 |
|
||||
17 |-foo() not in obj.keys() # SIM118
|
||||
17 |+foo() not in obj # SIM118
|
||||
18 18 |
|
||||
19 19 | for key in obj.keys(): # SIM118
|
||||
20 20 | pass
|
||||
|
||||
SIM118.py:19:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
17 | foo() not in obj.keys() # SIM118
|
||||
18 |
|
||||
19 | for key in obj.keys(): # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
20 | pass
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
16 16 |
|
||||
17 17 | foo() not in obj.keys() # SIM118
|
||||
18 18 |
|
||||
19 |-for key in obj.keys(): # SIM118
|
||||
19 |+for key in obj: # SIM118
|
||||
20 20 | pass
|
||||
21 21 |
|
||||
22 22 | for key in list(obj.keys()):
|
||||
|
||||
SIM118.py:26:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
24 | del obj[key]
|
||||
25 |
|
||||
26 | [k for k in obj.keys()] # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
27 |
|
||||
28 | {k for k in obj.keys()} # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
23 23 | if some_property(key):
|
||||
24 24 | del obj[key]
|
||||
25 25 |
|
||||
26 |-[k for k in obj.keys()] # SIM118
|
||||
26 |+[k for k in obj] # SIM118
|
||||
27 27 |
|
||||
28 28 | {k for k in obj.keys()} # SIM118
|
||||
29 29 |
|
||||
|
||||
SIM118.py:28:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
26 | [k for k in obj.keys()] # SIM118
|
||||
27 |
|
||||
28 | {k for k in obj.keys()} # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
29 |
|
||||
30 | {k: k for k in obj.keys()} # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
25 25 |
|
||||
26 26 | [k for k in obj.keys()] # SIM118
|
||||
27 27 |
|
||||
28 |-{k for k in obj.keys()} # SIM118
|
||||
28 |+{k for k in obj} # SIM118
|
||||
29 29 |
|
||||
30 30 | {k: k for k in obj.keys()} # SIM118
|
||||
31 31 |
|
||||
|
||||
SIM118.py:30:11: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
28 | {k for k in obj.keys()} # SIM118
|
||||
29 |
|
||||
30 | {k: k for k in obj.keys()} # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
31 |
|
||||
32 | (k for k in obj.keys()) # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
27 27 |
|
||||
28 28 | {k for k in obj.keys()} # SIM118
|
||||
29 29 |
|
||||
30 |-{k: k for k in obj.keys()} # SIM118
|
||||
30 |+{k: k for k in obj} # SIM118
|
||||
31 31 |
|
||||
32 32 | (k for k in obj.keys()) # SIM118
|
||||
33 33 |
|
||||
|
||||
SIM118.py:32:8: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
30 | {k: k for k in obj.keys()} # SIM118
|
||||
31 |
|
||||
32 | (k for k in obj.keys()) # SIM118
|
||||
| ^^^^^^^^^^^^^^^ SIM118
|
||||
33 |
|
||||
34 | key in (obj or {}).keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
29 29 |
|
||||
30 30 | {k: k for k in obj.keys()} # SIM118
|
||||
31 31 |
|
||||
32 |-(k for k in obj.keys()) # SIM118
|
||||
32 |+(k for k in obj) # SIM118
|
||||
33 33 |
|
||||
34 34 | key in (obj or {}).keys() # SIM118
|
||||
35 35 |
|
||||
|
||||
SIM118.py:34:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
32 | (k for k in obj.keys()) # SIM118
|
||||
33 |
|
||||
34 | key in (obj or {}).keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
35 |
|
||||
36 | (key) in (obj or {}).keys() # SIM118
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
31 31 |
|
||||
32 32 | (k for k in obj.keys()) # SIM118
|
||||
33 33 |
|
||||
34 |-key in (obj or {}).keys() # SIM118
|
||||
34 |+key in (obj or {}) # SIM118
|
||||
35 35 |
|
||||
36 36 | (key) in (obj or {}).keys() # SIM118
|
||||
37 37 |
|
||||
|
||||
SIM118.py:36:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
34 | key in (obj or {}).keys() # SIM118
|
||||
35 |
|
||||
36 | (key) in (obj or {}).keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
37 |
|
||||
38 | from typing import KeysView
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
33 33 |
|
||||
34 34 | key in (obj or {}).keys() # SIM118
|
||||
35 35 |
|
||||
36 |-(key) in (obj or {}).keys() # SIM118
|
||||
36 |+(key) in (obj or {}) # SIM118
|
||||
37 37 |
|
||||
38 38 | from typing import KeysView
|
||||
39 39 |
|
||||
|
||||
SIM118.py:50:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 | key in obj.keys()and foo
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
51 | (key in obj.keys())and foo
|
||||
52 | key in (obj.keys())and foo
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
47 47 |
|
||||
48 48 |
|
||||
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 |-key in obj.keys()and foo
|
||||
50 |+key in obj and foo
|
||||
51 51 | (key in obj.keys())and foo
|
||||
52 52 | key in (obj.keys())and foo
|
||||
53 53 |
|
||||
|
||||
SIM118.py:51:2: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 | key in obj.keys()and foo
|
||||
51 | (key in obj.keys())and foo
|
||||
| ^^^^^^^^^^^^^^^^^ SIM118
|
||||
52 | key in (obj.keys())and foo
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
48 48 |
|
||||
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 50 | key in obj.keys()and foo
|
||||
51 |-(key in obj.keys())and foo
|
||||
51 |+(key in obj)and foo
|
||||
52 52 | key in (obj.keys())and foo
|
||||
53 53 |
|
||||
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
|
||||
SIM118.py:52:1: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
50 | key in obj.keys()and foo
|
||||
51 | (key in obj.keys())and foo
|
||||
52 | key in (obj.keys())and foo
|
||||
| ^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
53 |
|
||||
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Fix
|
||||
49 49 | # Regression test for: https://github.com/astral-sh/ruff/issues/7124
|
||||
50 50 | key in obj.keys()and foo
|
||||
51 51 | (key in obj.keys())and foo
|
||||
52 |-key in (obj.keys())and foo
|
||||
52 |+key in (obj)and foo
|
||||
53 53 |
|
||||
54 54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
55 55 | for key in (
|
||||
|
||||
SIM118.py:55:5: SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
|
||||
|
|
||||
54 | # Regression test for: https://github.com/astral-sh/ruff/issues/7200
|
||||
55 | for key in (
|
||||
| _____^
|
||||
56 | | self.experiment.surveys[0]
|
||||
57 | | .stations[0]
|
||||
58 | | .keys()
|
||||
59 | | ):
|
||||
| |_^ SIM118
|
||||
60 | continue
|
||||
|
|
||||
= help: Remove `.keys()`
|
||||
|
||||
ℹ Suggested fix
|
||||
55 55 | for key in (
|
||||
56 56 | self.experiment.surveys[0]
|
||||
57 57 | .stations[0]
|
||||
58 |- .keys()
|
||||
58 |+
|
||||
59 59 | ):
|
||||
60 60 | continue
|
||||
|
||||
|
||||
157
crates/ruff_linter/src/rules/flake8_trio/method_name.rs
Normal file
157
crates/ruff_linter/src/rules/flake8_trio/method_name.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use ruff_python_ast::call_path::CallPath;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) enum MethodName {
|
||||
AcloseForcefully,
|
||||
CancelScope,
|
||||
CancelShieldedCheckpoint,
|
||||
Checkpoint,
|
||||
CheckpointIfCancelled,
|
||||
FailAfter,
|
||||
FailAt,
|
||||
MoveOnAfter,
|
||||
MoveOnAt,
|
||||
OpenFile,
|
||||
OpenProcess,
|
||||
OpenSslOverTcpListeners,
|
||||
OpenSslOverTcpStream,
|
||||
OpenTcpListeners,
|
||||
OpenTcpStream,
|
||||
OpenUnixSocket,
|
||||
PermanentlyDetachCoroutineObject,
|
||||
ReattachDetachedCoroutineObject,
|
||||
RunProcess,
|
||||
ServeListeners,
|
||||
ServeSslOverTcp,
|
||||
ServeTcp,
|
||||
Sleep,
|
||||
SleepForever,
|
||||
TemporarilyDetachCoroutineObject,
|
||||
WaitReadable,
|
||||
WaitTaskRescheduled,
|
||||
WaitWritable,
|
||||
}
|
||||
|
||||
impl MethodName {
|
||||
/// Returns `true` if the method is async, `false` if it is sync.
|
||||
pub(super) fn is_async(self) -> bool {
|
||||
match self {
|
||||
MethodName::AcloseForcefully
|
||||
| MethodName::CancelShieldedCheckpoint
|
||||
| MethodName::Checkpoint
|
||||
| MethodName::CheckpointIfCancelled
|
||||
| MethodName::OpenFile
|
||||
| MethodName::OpenProcess
|
||||
| MethodName::OpenSslOverTcpListeners
|
||||
| MethodName::OpenSslOverTcpStream
|
||||
| MethodName::OpenTcpListeners
|
||||
| MethodName::OpenTcpStream
|
||||
| MethodName::OpenUnixSocket
|
||||
| MethodName::PermanentlyDetachCoroutineObject
|
||||
| MethodName::ReattachDetachedCoroutineObject
|
||||
| MethodName::RunProcess
|
||||
| MethodName::ServeListeners
|
||||
| MethodName::ServeSslOverTcp
|
||||
| MethodName::ServeTcp
|
||||
| MethodName::Sleep
|
||||
| MethodName::SleepForever
|
||||
| MethodName::TemporarilyDetachCoroutineObject
|
||||
| MethodName::WaitReadable
|
||||
| MethodName::WaitTaskRescheduled
|
||||
| MethodName::WaitWritable => true,
|
||||
|
||||
MethodName::MoveOnAfter
|
||||
| MethodName::MoveOnAt
|
||||
| MethodName::FailAfter
|
||||
| MethodName::FailAt
|
||||
| MethodName::CancelScope => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MethodName {
|
||||
pub(super) fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
|
||||
match call_path.as_slice() {
|
||||
["trio", "CancelScope"] => Some(Self::CancelScope),
|
||||
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully),
|
||||
["trio", "fail_after"] => Some(Self::FailAfter),
|
||||
["trio", "fail_at"] => Some(Self::FailAt),
|
||||
["trio", "lowlevel", "cancel_shielded_checkpoint"] => {
|
||||
Some(Self::CancelShieldedCheckpoint)
|
||||
}
|
||||
["trio", "lowlevel", "checkpoint"] => Some(Self::Checkpoint),
|
||||
["trio", "lowlevel", "checkpoint_if_cancelled"] => Some(Self::CheckpointIfCancelled),
|
||||
["trio", "lowlevel", "open_process"] => Some(Self::OpenProcess),
|
||||
["trio", "lowlevel", "permanently_detach_coroutine_object"] => {
|
||||
Some(Self::PermanentlyDetachCoroutineObject)
|
||||
}
|
||||
["trio", "lowlevel", "reattach_detached_coroutine_object"] => {
|
||||
Some(Self::ReattachDetachedCoroutineObject)
|
||||
}
|
||||
["trio", "lowlevel", "temporarily_detach_coroutine_object"] => {
|
||||
Some(Self::TemporarilyDetachCoroutineObject)
|
||||
}
|
||||
["trio", "lowlevel", "wait_readable"] => Some(Self::WaitReadable),
|
||||
["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::WaitTaskRescheduled),
|
||||
["trio", "lowlevel", "wait_writable"] => Some(Self::WaitWritable),
|
||||
["trio", "move_on_after"] => Some(Self::MoveOnAfter),
|
||||
["trio", "move_on_at"] => Some(Self::MoveOnAt),
|
||||
["trio", "open_file"] => Some(Self::OpenFile),
|
||||
["trio", "open_ssl_over_tcp_listeners"] => Some(Self::OpenSslOverTcpListeners),
|
||||
["trio", "open_ssl_over_tcp_stream"] => Some(Self::OpenSslOverTcpStream),
|
||||
["trio", "open_tcp_listeners"] => Some(Self::OpenTcpListeners),
|
||||
["trio", "open_tcp_stream"] => Some(Self::OpenTcpStream),
|
||||
["trio", "open_unix_socket"] => Some(Self::OpenUnixSocket),
|
||||
["trio", "run_process"] => Some(Self::RunProcess),
|
||||
["trio", "serve_listeners"] => Some(Self::ServeListeners),
|
||||
["trio", "serve_ssl_over_tcp"] => Some(Self::ServeSslOverTcp),
|
||||
["trio", "serve_tcp"] => Some(Self::ServeTcp),
|
||||
["trio", "sleep"] => Some(Self::Sleep),
|
||||
["trio", "sleep_forever"] => Some(Self::SleepForever),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MethodName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MethodName::AcloseForcefully => write!(f, "trio.aclose_forcefully"),
|
||||
MethodName::CancelScope => write!(f, "trio.CancelScope"),
|
||||
MethodName::CancelShieldedCheckpoint => {
|
||||
write!(f, "trio.lowlevel.cancel_shielded_checkpoint")
|
||||
}
|
||||
MethodName::Checkpoint => write!(f, "trio.lowlevel.checkpoint"),
|
||||
MethodName::CheckpointIfCancelled => write!(f, "trio.lowlevel.checkpoint_if_cancelled"),
|
||||
MethodName::FailAfter => write!(f, "trio.fail_after"),
|
||||
MethodName::FailAt => write!(f, "trio.fail_at"),
|
||||
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"),
|
||||
MethodName::MoveOnAt => write!(f, "trio.move_on_at"),
|
||||
MethodName::OpenFile => write!(f, "trio.open_file"),
|
||||
MethodName::OpenProcess => write!(f, "trio.lowlevel.open_process"),
|
||||
MethodName::OpenSslOverTcpListeners => write!(f, "trio.open_ssl_over_tcp_listeners"),
|
||||
MethodName::OpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"),
|
||||
MethodName::OpenTcpListeners => write!(f, "trio.open_tcp_listeners"),
|
||||
MethodName::OpenTcpStream => write!(f, "trio.open_tcp_stream"),
|
||||
MethodName::OpenUnixSocket => write!(f, "trio.open_unix_socket"),
|
||||
MethodName::PermanentlyDetachCoroutineObject => {
|
||||
write!(f, "trio.lowlevel.permanently_detach_coroutine_object")
|
||||
}
|
||||
MethodName::ReattachDetachedCoroutineObject => {
|
||||
write!(f, "trio.lowlevel.reattach_detached_coroutine_object")
|
||||
}
|
||||
MethodName::RunProcess => write!(f, "trio.run_process"),
|
||||
MethodName::ServeListeners => write!(f, "trio.serve_listeners"),
|
||||
MethodName::ServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"),
|
||||
MethodName::ServeTcp => write!(f, "trio.serve_tcp"),
|
||||
MethodName::Sleep => write!(f, "trio.sleep"),
|
||||
MethodName::SleepForever => write!(f, "trio.sleep_forever"),
|
||||
MethodName::TemporarilyDetachCoroutineObject => {
|
||||
write!(f, "trio.lowlevel.temporarily_detach_coroutine_object")
|
||||
}
|
||||
MethodName::WaitReadable => write!(f, "trio.lowlevel.wait_readable"),
|
||||
MethodName::WaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"),
|
||||
MethodName::WaitWritable => write!(f, "trio.lowlevel.wait_writable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Rules from [flake8-trio](https://pypi.org/project/flake8-trio/).
|
||||
pub(super) mod method_name;
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -14,6 +15,8 @@ mod tests {
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("TRIO100.py"))]
|
||||
#[test_case(Rule::TrioSyncCall, Path::new("TRIO105.py"))]
|
||||
#[test_case(Rule::TrioZeroSleepCall, Path::new("TRIO115.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
pub(crate) use sync_call::*;
|
||||
pub(crate) use timeout_without_await::*;
|
||||
pub(crate) use zero_sleep_call::*;
|
||||
|
||||
mod sync_call;
|
||||
mod timeout_without_await;
|
||||
mod zero_sleep_call;
|
||||
|
||||
87
crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs
Normal file
87
crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::pad;
|
||||
use crate::rules::flake8_trio::method_name::MethodName;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for calls to trio functions that are not immediately awaited.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Many of the functions exposed by trio are asynchronous, and must be awaited
|
||||
/// to take effect. Calling a trio function without an `await` can lead to
|
||||
/// `RuntimeWarning` diagnostics and unexpected behaviour.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def double_sleep(x):
|
||||
/// trio.sleep(2 * x)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// async def double_sleep(x):
|
||||
/// await trio.sleep(2 * x)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as adding an `await` to a function
|
||||
/// call changes its semantics and runtime behavior.
|
||||
#[violation]
|
||||
pub struct TrioSyncCall {
|
||||
method_name: MethodName,
|
||||
}
|
||||
|
||||
impl Violation for TrioSyncCall {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let Self { method_name } = self;
|
||||
format!("Call to `{method_name}` is not immediately awaited")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some(format!("Add `await`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// TRIO105
|
||||
pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
|
||||
let Some(method_name) = ({
|
||||
let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else {
|
||||
return;
|
||||
};
|
||||
MethodName::try_from(&call_path)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !method_name.is_async() {
|
||||
return;
|
||||
}
|
||||
|
||||
if checker
|
||||
.semantic()
|
||||
.current_expression_parent()
|
||||
.is_some_and(Expr::is_await_expr)
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(TrioSyncCall { method_name }, call.range);
|
||||
if checker.semantic().in_async_context() {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
pad(
|
||||
"await".to_string(),
|
||||
TextRange::new(call.func.start(), call.func.start()),
|
||||
checker.locator(),
|
||||
),
|
||||
call.func.start(),
|
||||
)));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor};
|
||||
use ruff_python_ast::{Expr, ExprAwait, Stmt, StmtWith, WithItem};
|
||||
use ruff_python_ast::helpers::AwaitVisitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{StmtWith, WithItem};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_trio::method_name::MethodName;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for trio functions that should contain await but don't.
|
||||
@@ -56,6 +57,17 @@ pub(crate) fn timeout_without_await(
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
method_name,
|
||||
MethodName::MoveOnAfter
|
||||
| MethodName::MoveOnAt
|
||||
| MethodName::FailAfter
|
||||
| MethodName::FailAt
|
||||
| MethodName::CancelScope
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut visitor = AwaitVisitor::default();
|
||||
visitor.visit_body(&with_stmt.body);
|
||||
if visitor.seen_await {
|
||||
@@ -67,59 +79,3 @@ pub(crate) fn timeout_without_await(
|
||||
with_stmt.range,
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum MethodName {
|
||||
MoveOnAfter,
|
||||
MoveOnAt,
|
||||
FailAfter,
|
||||
FailAt,
|
||||
CancelScope,
|
||||
}
|
||||
|
||||
impl MethodName {
|
||||
fn try_from(call_path: &CallPath<'_>) -> Option<Self> {
|
||||
match call_path.as_slice() {
|
||||
["trio", "move_on_after"] => Some(Self::MoveOnAfter),
|
||||
["trio", "move_on_at"] => Some(Self::MoveOnAt),
|
||||
["trio", "fail_after"] => Some(Self::FailAfter),
|
||||
["trio", "fail_at"] => Some(Self::FailAt),
|
||||
["trio", "CancelScope"] => Some(Self::CancelScope),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MethodName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"),
|
||||
MethodName::MoveOnAt => write!(f, "trio.move_on_at"),
|
||||
MethodName::FailAfter => write!(f, "trio.fail_after"),
|
||||
MethodName::FailAt => write!(f, "trio.fail_at"),
|
||||
MethodName::CancelScope => write!(f, "trio.CancelScope"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct AwaitVisitor {
|
||||
seen_await: bool,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for AwaitVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
|
||||
_ => walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
if let Expr::Await(ExprAwait { .. }) = expr {
|
||||
self.seen_await = true;
|
||||
} else {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::Stmt;
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall, Int};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `trio.sleep(0)`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `trio.sleep(0)` is equivalent to calling `trio.lowlevel.checkpoint()`.
|
||||
/// However, the latter better conveys the intent of the code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// async def func():
|
||||
/// await trio.sleep(0)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// async def func():
|
||||
/// await trio.lowlevel.checkpoint()
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct TrioZeroSleepCall;
|
||||
|
||||
impl AlwaysFixableViolation for TrioZeroSleepCall {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Replace with `trio.lowlevel.checkpoint()`")
|
||||
}
|
||||
}
|
||||
|
||||
/// TRIO115
|
||||
pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(call.func.as_ref())
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["trio", "sleep"]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument("seconds", 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match arg {
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
|
||||
let Some(int) = value.as_int() else { return };
|
||||
if *int != Int::ZERO {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
let scope = checker.semantic().current_scope();
|
||||
if let Some(binding_id) = scope.get(id) {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
|
||||
if let Some(parent_id) = binding.source {
|
||||
let parent = checker.semantic().statement(parent_id);
|
||||
if let Stmt::Assign(ast::StmtAssign { value, .. })
|
||||
| Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
value: Some(value), ..
|
||||
})
|
||||
| Stmt::AugAssign(ast::StmtAugAssign { value, .. }) = parent
|
||||
{
|
||||
let Expr::NumberLiteral(ast::ExprNumberLiteral { value: num, .. }) =
|
||||
value.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(int) = num.as_int() else { return };
|
||||
if *int != Int::ZERO {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(TrioZeroSleepCall, call.range());
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("trio", "lowlevel.checkpoint"),
|
||||
call.func.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let reference_edit = Edit::range_replacement(binding, call.func.range());
|
||||
let arg_edit = Edit::range_deletion(call.arguments.range);
|
||||
Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit]))
|
||||
});
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
|
||||
---
|
||||
TRIO105.py:30:5: TRIO105 [*] Call to `trio.aclose_forcefully` is not immediately awaited
|
||||
|
|
||||
29 | # TRIO105
|
||||
30 | trio.aclose_forcefully(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
31 | trio.open_file(foo)
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
27 27 | await trio.lowlevel.wait_writable(foo)
|
||||
28 28 |
|
||||
29 29 | # TRIO105
|
||||
30 |- trio.aclose_forcefully(foo)
|
||||
30 |+ await trio.aclose_forcefully(foo)
|
||||
31 31 | trio.open_file(foo)
|
||||
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
|
||||
TRIO105.py:31:5: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
|
|
||||
29 | # TRIO105
|
||||
30 | trio.aclose_forcefully(foo)
|
||||
31 | trio.open_file(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
28 28 |
|
||||
29 29 | # TRIO105
|
||||
30 30 | trio.aclose_forcefully(foo)
|
||||
31 |- trio.open_file(foo)
|
||||
31 |+ await trio.open_file(foo)
|
||||
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
|
||||
TRIO105.py:32:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_listeners` is not immediately awaited
|
||||
|
|
||||
30 | trio.aclose_forcefully(foo)
|
||||
31 | trio.open_file(foo)
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
29 29 | # TRIO105
|
||||
30 30 | trio.aclose_forcefully(foo)
|
||||
31 31 | trio.open_file(foo)
|
||||
32 |- trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
32 |+ await trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
|
||||
TRIO105.py:33:5: TRIO105 [*] Call to `trio.open_ssl_over_tcp_stream` is not immediately awaited
|
||||
|
|
||||
31 | trio.open_file(foo)
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
30 30 | trio.aclose_forcefully(foo)
|
||||
31 31 | trio.open_file(foo)
|
||||
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 |- trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
33 |+ await trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
|
||||
TRIO105.py:34:5: TRIO105 [*] Call to `trio.open_tcp_listeners` is not immediately awaited
|
||||
|
|
||||
32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
36 | trio.open_unix_socket(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
31 31 | trio.open_file(foo)
|
||||
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 |- trio.open_tcp_listeners(foo)
|
||||
34 |+ await trio.open_tcp_listeners(foo)
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
37 37 | trio.run_process(foo)
|
||||
|
||||
TRIO105.py:35:5: TRIO105 [*] Call to `trio.open_tcp_stream` is not immediately awaited
|
||||
|
|
||||
33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
36 | trio.open_unix_socket(foo)
|
||||
37 | trio.run_process(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
32 32 | trio.open_ssl_over_tcp_listeners(foo, foo)
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
35 |- trio.open_tcp_stream(foo, foo)
|
||||
35 |+ await trio.open_tcp_stream(foo, foo)
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
37 37 | trio.run_process(foo)
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
|
||||
TRIO105.py:36:5: TRIO105 [*] Call to `trio.open_unix_socket` is not immediately awaited
|
||||
|
|
||||
34 | trio.open_tcp_listeners(foo)
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
36 | trio.open_unix_socket(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
37 | trio.run_process(foo)
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
33 33 | trio.open_ssl_over_tcp_stream(foo, foo)
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
36 |- trio.open_unix_socket(foo)
|
||||
36 |+ await trio.open_unix_socket(foo)
|
||||
37 37 | trio.run_process(foo)
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
|
||||
TRIO105.py:37:5: TRIO105 [*] Call to `trio.run_process` is not immediately awaited
|
||||
|
|
||||
35 | trio.open_tcp_stream(foo, foo)
|
||||
36 | trio.open_unix_socket(foo)
|
||||
37 | trio.run_process(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
34 34 | trio.open_tcp_listeners(foo)
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
37 |- trio.run_process(foo)
|
||||
37 |+ await trio.run_process(foo)
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 40 | trio.serve_tcp(foo, foo)
|
||||
|
||||
TRIO105.py:38:5: TRIO105 [*] Call to `trio.serve_listeners` is not immediately awaited
|
||||
|
|
||||
36 | trio.open_unix_socket(foo)
|
||||
37 | trio.run_process(foo)
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
35 35 | trio.open_tcp_stream(foo, foo)
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
37 37 | trio.run_process(foo)
|
||||
38 |- trio.serve_listeners(foo, foo)
|
||||
38 |+ await trio.serve_listeners(foo, foo)
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 40 | trio.serve_tcp(foo, foo)
|
||||
41 41 | trio.sleep(foo)
|
||||
|
||||
TRIO105.py:39:5: TRIO105 [*] Call to `trio.serve_ssl_over_tcp` is not immediately awaited
|
||||
|
|
||||
37 | trio.run_process(foo)
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
41 | trio.sleep(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
36 36 | trio.open_unix_socket(foo)
|
||||
37 37 | trio.run_process(foo)
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
39 |- trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
39 |+ await trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 40 | trio.serve_tcp(foo, foo)
|
||||
41 41 | trio.sleep(foo)
|
||||
42 42 | trio.sleep_forever()
|
||||
|
||||
TRIO105.py:40:5: TRIO105 [*] Call to `trio.serve_tcp` is not immediately awaited
|
||||
|
|
||||
38 | trio.serve_listeners(foo, foo)
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
41 | trio.sleep(foo)
|
||||
42 | trio.sleep_forever()
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
37 37 | trio.run_process(foo)
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 |- trio.serve_tcp(foo, foo)
|
||||
40 |+ await trio.serve_tcp(foo, foo)
|
||||
41 41 | trio.sleep(foo)
|
||||
42 42 | trio.sleep_forever()
|
||||
43 43 | trio.sleep_until(foo)
|
||||
|
||||
TRIO105.py:41:5: TRIO105 [*] Call to `trio.sleep` is not immediately awaited
|
||||
|
|
||||
39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
41 | trio.sleep(foo)
|
||||
| ^^^^^^^^^^^^^^^ TRIO105
|
||||
42 | trio.sleep_forever()
|
||||
43 | trio.sleep_until(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
38 38 | trio.serve_listeners(foo, foo)
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 40 | trio.serve_tcp(foo, foo)
|
||||
41 |- trio.sleep(foo)
|
||||
41 |+ await trio.sleep(foo)
|
||||
42 42 | trio.sleep_forever()
|
||||
43 43 | trio.sleep_until(foo)
|
||||
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
|
||||
TRIO105.py:42:5: TRIO105 [*] Call to `trio.sleep_forever` is not immediately awaited
|
||||
|
|
||||
40 | trio.serve_tcp(foo, foo)
|
||||
41 | trio.sleep(foo)
|
||||
42 | trio.sleep_forever()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
43 | trio.sleep_until(foo)
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
39 39 | trio.serve_ssl_over_tcp(foo, foo, foo)
|
||||
40 40 | trio.serve_tcp(foo, foo)
|
||||
41 41 | trio.sleep(foo)
|
||||
42 |- trio.sleep_forever()
|
||||
42 |+ await trio.sleep_forever()
|
||||
43 43 | trio.sleep_until(foo)
|
||||
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 45 | trio.lowlevel.checkpoint()
|
||||
|
||||
TRIO105.py:44:5: TRIO105 [*] Call to `trio.lowlevel.cancel_shielded_checkpoint` is not immediately awaited
|
||||
|
|
||||
42 | trio.sleep_forever()
|
||||
43 | trio.sleep_until(foo)
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
41 41 | trio.sleep(foo)
|
||||
42 42 | trio.sleep_forever()
|
||||
43 43 | trio.sleep_until(foo)
|
||||
44 |- trio.lowlevel.cancel_shielded_checkpoint()
|
||||
44 |+ await trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 45 | trio.lowlevel.checkpoint()
|
||||
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
|
||||
TRIO105.py:45:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint` is not immediately awaited
|
||||
|
|
||||
43 | trio.sleep_until(foo)
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 | trio.lowlevel.open_process()
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
42 42 | trio.sleep_forever()
|
||||
43 43 | trio.sleep_until(foo)
|
||||
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 |- trio.lowlevel.checkpoint()
|
||||
45 |+ await trio.lowlevel.checkpoint()
|
||||
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
|
||||
TRIO105.py:46:5: TRIO105 [*] Call to `trio.lowlevel.checkpoint_if_cancelled` is not immediately awaited
|
||||
|
|
||||
44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
47 | trio.lowlevel.open_process()
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
43 43 | trio.sleep_until(foo)
|
||||
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 45 | trio.lowlevel.checkpoint()
|
||||
46 |- trio.lowlevel.checkpoint_if_cancelled()
|
||||
46 |+ await trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
|
||||
TRIO105.py:47:5: TRIO105 [*] Call to `trio.lowlevel.open_process` is not immediately awaited
|
||||
|
|
||||
45 | trio.lowlevel.checkpoint()
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 | trio.lowlevel.open_process()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
44 44 | trio.lowlevel.cancel_shielded_checkpoint()
|
||||
45 45 | trio.lowlevel.checkpoint()
|
||||
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 |- trio.lowlevel.open_process()
|
||||
47 |+ await trio.lowlevel.open_process()
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
|
||||
TRIO105.py:48:5: TRIO105 [*] Call to `trio.lowlevel.permanently_detach_coroutine_object` is not immediately awaited
|
||||
|
|
||||
46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 | trio.lowlevel.open_process()
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
45 45 | trio.lowlevel.checkpoint()
|
||||
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
48 |- trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
48 |+ await trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 51 | trio.lowlevel.wait_readable(foo)
|
||||
|
||||
TRIO105.py:49:5: TRIO105 [*] Call to `trio.lowlevel.reattach_detached_coroutine_object` is not immediately awaited
|
||||
|
|
||||
47 | trio.lowlevel.open_process()
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
46 46 | trio.lowlevel.checkpoint_if_cancelled()
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 |- trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
49 |+ await trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 51 | trio.lowlevel.wait_readable(foo)
|
||||
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
|
||||
TRIO105.py:50:5: TRIO105 [*] Call to `trio.lowlevel.temporarily_detach_coroutine_object` is not immediately awaited
|
||||
|
|
||||
48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
47 47 | trio.lowlevel.open_process()
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 |- trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
50 |+ await trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 51 | trio.lowlevel.wait_readable(foo)
|
||||
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 53 | trio.lowlevel.wait_writable(foo)
|
||||
|
||||
TRIO105.py:51:5: TRIO105 [*] Call to `trio.lowlevel.wait_readable` is not immediately awaited
|
||||
|
|
||||
49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 | trio.lowlevel.wait_writable(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
48 48 | trio.lowlevel.permanently_detach_coroutine_object(foo)
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 |- trio.lowlevel.wait_readable(foo)
|
||||
51 |+ await trio.lowlevel.wait_readable(foo)
|
||||
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 53 | trio.lowlevel.wait_writable(foo)
|
||||
54 54 |
|
||||
|
||||
TRIO105.py:52:5: TRIO105 [*] Call to `trio.lowlevel.wait_task_rescheduled` is not immediately awaited
|
||||
|
|
||||
50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
53 | trio.lowlevel.wait_writable(foo)
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
49 49 | trio.lowlevel.reattach_detached_coroutine_object(foo, foo)
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 51 | trio.lowlevel.wait_readable(foo)
|
||||
52 |- trio.lowlevel.wait_task_rescheduled(foo)
|
||||
52 |+ await trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 53 | trio.lowlevel.wait_writable(foo)
|
||||
54 54 |
|
||||
55 55 | async with await trio.open_file(foo): # Ok
|
||||
|
||||
TRIO105.py:53:5: TRIO105 [*] Call to `trio.lowlevel.wait_writable` is not immediately awaited
|
||||
|
|
||||
51 | trio.lowlevel.wait_readable(foo)
|
||||
52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 | trio.lowlevel.wait_writable(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
54 |
|
||||
55 | async with await trio.open_file(foo): # Ok
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
50 50 | trio.lowlevel.temporarily_detach_coroutine_object(foo)
|
||||
51 51 | trio.lowlevel.wait_readable(foo)
|
||||
52 52 | trio.lowlevel.wait_task_rescheduled(foo)
|
||||
53 |- trio.lowlevel.wait_writable(foo)
|
||||
53 |+ await trio.lowlevel.wait_writable(foo)
|
||||
54 54 |
|
||||
55 55 | async with await trio.open_file(foo): # Ok
|
||||
56 56 | pass
|
||||
|
||||
TRIO105.py:58:16: TRIO105 [*] Call to `trio.open_file` is not immediately awaited
|
||||
|
|
||||
56 | pass
|
||||
57 |
|
||||
58 | async with trio.open_file(foo): # TRIO105
|
||||
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
59 | pass
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
ℹ Suggested fix
|
||||
55 55 | async with await trio.open_file(foo): # Ok
|
||||
56 56 | pass
|
||||
57 57 |
|
||||
58 |- async with trio.open_file(foo): # TRIO105
|
||||
58 |+ async with await trio.open_file(foo): # TRIO105
|
||||
59 59 | pass
|
||||
60 60 |
|
||||
61 61 |
|
||||
|
||||
TRIO105.py:64:5: TRIO105 Call to `trio.open_file` is not immediately awaited
|
||||
|
|
||||
62 | def func() -> None:
|
||||
63 | # TRIO105 (without fix)
|
||||
64 | trio.open_file(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^ TRIO105
|
||||
|
|
||||
= help: Add `await`
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
|
||||
---
|
||||
TRIO115.py:6:11: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
||||
|
|
||||
5 | async def func():
|
||||
6 | await trio.sleep(0) # TRIO115
|
||||
| ^^^^^^^^^^^^^ TRIO115
|
||||
7 | await trio.sleep(1) # OK
|
||||
8 | await trio.sleep(0, 1) # OK
|
||||
|
|
||||
= help: Replace with `trio.lowlevel.checkpoint()`
|
||||
|
||||
ℹ Fix
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 5 | async def func():
|
||||
6 |- await trio.sleep(0) # TRIO115
|
||||
6 |+ await trio.lowlevel.checkpoint # TRIO115
|
||||
7 7 | await trio.sleep(1) # OK
|
||||
8 8 | await trio.sleep(0, 1) # OK
|
||||
9 9 | await trio.sleep(...) # OK
|
||||
|
||||
TRIO115.py:12:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
||||
|
|
||||
10 | await trio.sleep() # OK
|
||||
11 |
|
||||
12 | trio.sleep(0) # TRIO115
|
||||
| ^^^^^^^^^^^^^ TRIO115
|
||||
13 | foo = 0
|
||||
14 | trio.sleep(foo) # TRIO115
|
||||
|
|
||||
= help: Replace with `trio.lowlevel.checkpoint()`
|
||||
|
||||
ℹ Fix
|
||||
9 9 | await trio.sleep(...) # OK
|
||||
10 10 | await trio.sleep() # OK
|
||||
11 11 |
|
||||
12 |- trio.sleep(0) # TRIO115
|
||||
12 |+ trio.lowlevel.checkpoint # TRIO115
|
||||
13 13 | foo = 0
|
||||
14 14 | trio.sleep(foo) # TRIO115
|
||||
15 15 | trio.sleep(1) # OK
|
||||
|
||||
TRIO115.py:14:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
||||
|
|
||||
12 | trio.sleep(0) # TRIO115
|
||||
13 | foo = 0
|
||||
14 | trio.sleep(foo) # TRIO115
|
||||
| ^^^^^^^^^^^^^^^ TRIO115
|
||||
15 | trio.sleep(1) # OK
|
||||
16 | time.sleep(0) # OK
|
||||
|
|
||||
= help: Replace with `trio.lowlevel.checkpoint()`
|
||||
|
||||
ℹ Fix
|
||||
11 11 |
|
||||
12 12 | trio.sleep(0) # TRIO115
|
||||
13 13 | foo = 0
|
||||
14 |- trio.sleep(foo) # TRIO115
|
||||
14 |+ trio.lowlevel.checkpoint # TRIO115
|
||||
15 15 | trio.sleep(1) # OK
|
||||
16 16 | time.sleep(0) # OK
|
||||
17 17 |
|
||||
|
||||
TRIO115.py:18:5: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
||||
|
|
||||
16 | time.sleep(0) # OK
|
||||
17 |
|
||||
18 | sleep(0) # TRIO115
|
||||
| ^^^^^^^^ TRIO115
|
||||
19 |
|
||||
20 | bar = "bar"
|
||||
|
|
||||
= help: Replace with `trio.lowlevel.checkpoint()`
|
||||
|
||||
ℹ Fix
|
||||
15 15 | trio.sleep(1) # OK
|
||||
16 16 | time.sleep(0) # OK
|
||||
17 17 |
|
||||
18 |- sleep(0) # TRIO115
|
||||
18 |+ trio.lowlevel.checkpoint # TRIO115
|
||||
19 19 |
|
||||
20 20 | bar = "bar"
|
||||
21 21 | trio.sleep(bar)
|
||||
|
||||
TRIO115.py:24:1: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
||||
|
|
||||
24 | trio.sleep(0) # TRIO115
|
||||
| ^^^^^^^^^^^^^ TRIO115
|
||||
|
|
||||
= help: Replace with `trio.lowlevel.checkpoint()`
|
||||
|
||||
ℹ Fix
|
||||
21 21 | trio.sleep(bar)
|
||||
22 22 |
|
||||
23 23 |
|
||||
24 |-trio.sleep(0) # TRIO115
|
||||
24 |+trio.lowlevel.checkpoint # TRIO115
|
||||
25 25 |
|
||||
26 26 |
|
||||
27 27 | def func():
|
||||
|
||||
TRIO115.py:28:14: TRIO115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`
|
||||
|
|
||||
27 | def func():
|
||||
28 | trio.run(trio.sleep(0)) # TRIO115
|
||||
| ^^^^^^^^^^^^^ TRIO115
|
||||
|
|
||||
= help: Replace with `trio.lowlevel.checkpoint()`
|
||||
|
||||
ℹ Fix
|
||||
25 25 |
|
||||
26 26 |
|
||||
27 27 | def func():
|
||||
28 |- trio.run(trio.sleep(0)) # TRIO115
|
||||
28 |+ trio.run(trio.lowlevel.checkpoint) # TRIO115
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::analyze::typing::is_dict;
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -129,22 +128,16 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
|
||||
}
|
||||
|
||||
// Exclude non-dictionary value.
|
||||
let Expr::Name(ast::ExprName {
|
||||
id: subscript_name, ..
|
||||
}) = subscript_value.as_ref()
|
||||
let Some(name) = subscript_value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(binding) = checker
|
||||
.semantic()
|
||||
.only_binding(name)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let scope = checker.semantic().current_scope();
|
||||
let bindings: Vec<&Binding> = scope
|
||||
.get_all(subscript_name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.collect();
|
||||
|
||||
let [binding] = bindings.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !is_dict(binding, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
@@ -165,8 +158,7 @@ pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, bo
|
||||
// ```
|
||||
if if_test.is_some_and(|test| {
|
||||
any_over_expr(test, &|expr| {
|
||||
expr.as_name_expr()
|
||||
.is_some_and(|expr| expr.id == *subscript_name)
|
||||
ComparableExpr::from(expr) == ComparableExpr::from(name)
|
||||
})
|
||||
}) {
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,6 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_semantic::analyze::typing::is_list;
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -144,20 +143,16 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
|
||||
}
|
||||
|
||||
// Avoid non-list values.
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
let Some(name) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let bindings: Vec<&Binding> = checker
|
||||
let Some(binding) = checker
|
||||
.semantic()
|
||||
.current_scope()
|
||||
.get_all(id)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.collect();
|
||||
|
||||
let [binding] = bindings.as_slice() else {
|
||||
.only_binding(name)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !is_list(binding, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
@@ -176,15 +171,12 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
|
||||
// ```python
|
||||
// filtered = [x for x in y if x in filtered]
|
||||
// ```
|
||||
if let Some(value_name) = value.as_name_expr() {
|
||||
if if_test.is_some_and(|test| {
|
||||
any_over_expr(test, &|expr| {
|
||||
expr.as_name_expr()
|
||||
.is_some_and(|expr| expr.id == value_name.id)
|
||||
})
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
if if_test.is_some_and(|test| {
|
||||
any_over_expr(test, &|expr| {
|
||||
expr.as_name_expr().is_some_and(|expr| expr.id == name.id)
|
||||
})
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
|
||||
@@ -3,7 +3,6 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
use ruff_python_semantic::analyze::typing::is_list;
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -102,20 +101,16 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
|
||||
}
|
||||
|
||||
// Avoid non-list values.
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
let Some(name) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let bindings: Vec<&Binding> = checker
|
||||
let Some(binding) = checker
|
||||
.semantic()
|
||||
.current_scope()
|
||||
.get_all(id)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.collect();
|
||||
|
||||
let [binding] = bindings.as_slice() else {
|
||||
.only_binding(name)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !is_list(binding, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use ruff_source_file::Locator;
|
||||
/// if foo == "blah":
|
||||
/// do_blah_thing()
|
||||
/// ```
|
||||
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#other-recommendations
|
||||
#[violation]
|
||||
pub struct MultipleStatementsOnOneLineColon;
|
||||
@@ -206,12 +206,13 @@ pub(crate) fn compound_statements(
|
||||
{
|
||||
colon = Some((range.start(), range.end()));
|
||||
|
||||
// Allow `class C: ...`-style definitions in stubs.
|
||||
allow_ellipsis = class.is_some();
|
||||
// Allow `class C: ...`-style definitions.
|
||||
allow_ellipsis = true;
|
||||
}
|
||||
}
|
||||
Tok::Semi => {
|
||||
semi = Some((range.start(), range.end()));
|
||||
allow_ellipsis = false;
|
||||
}
|
||||
Tok::Comment(..) | Tok::Indent | Tok::Dedent | Tok::NonLogicalNewline => {}
|
||||
_ => {
|
||||
@@ -223,6 +224,7 @@ pub(crate) fn compound_statements(
|
||||
|
||||
// Reset.
|
||||
semi = None;
|
||||
allow_ellipsis = false;
|
||||
}
|
||||
|
||||
if let Some((start, end)) = colon {
|
||||
@@ -245,6 +247,7 @@ pub(crate) fn compound_statements(
|
||||
try_ = None;
|
||||
while_ = None;
|
||||
with = None;
|
||||
allow_ellipsis = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ fn deprecated_type_comparison(checker: &mut Checker, compare: &ast::ExprCompare)
|
||||
| "list"
|
||||
| "dict"
|
||||
| "set"
|
||||
| "memoryview"
|
||||
) && checker.semantic().is_builtin(id)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
@@ -197,7 +198,98 @@ fn is_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
// Ex) `type(obj) == int`
|
||||
matches!(
|
||||
id.as_str(),
|
||||
"int" | "str" | "float" | "bool" | "complex" | "bytes" | "list" | "dict" | "set"
|
||||
"bool"
|
||||
| "bytearray"
|
||||
| "bytes"
|
||||
| "classmethod"
|
||||
| "complex"
|
||||
| "dict"
|
||||
| "enumerate"
|
||||
| "filter"
|
||||
| "float"
|
||||
| "frozenset"
|
||||
| "int"
|
||||
| "list"
|
||||
| "map"
|
||||
| "memoryview"
|
||||
| "object"
|
||||
| "property"
|
||||
| "range"
|
||||
| "reversed"
|
||||
| "set"
|
||||
| "slice"
|
||||
| "staticmethod"
|
||||
| "str"
|
||||
| "super"
|
||||
| "tuple"
|
||||
| "type"
|
||||
| "zip"
|
||||
| "ArithmeticError"
|
||||
| "AssertionError"
|
||||
| "AttributeError"
|
||||
| "BaseException"
|
||||
| "BlockingIOError"
|
||||
| "BrokenPipeError"
|
||||
| "BufferError"
|
||||
| "BytesWarning"
|
||||
| "ChildProcessError"
|
||||
| "ConnectionAbortedError"
|
||||
| "ConnectionError"
|
||||
| "ConnectionRefusedError"
|
||||
| "ConnectionResetError"
|
||||
| "DeprecationWarning"
|
||||
| "EnvironmentError"
|
||||
| "EOFError"
|
||||
| "Exception"
|
||||
| "FileExistsError"
|
||||
| "FileNotFoundError"
|
||||
| "FloatingPointError"
|
||||
| "FutureWarning"
|
||||
| "GeneratorExit"
|
||||
| "ImportError"
|
||||
| "ImportWarning"
|
||||
| "IndentationError"
|
||||
| "IndexError"
|
||||
| "InterruptedError"
|
||||
| "IOError"
|
||||
| "IsADirectoryError"
|
||||
| "KeyboardInterrupt"
|
||||
| "KeyError"
|
||||
| "LookupError"
|
||||
| "MemoryError"
|
||||
| "ModuleNotFoundError"
|
||||
| "NameError"
|
||||
| "NotADirectoryError"
|
||||
| "NotImplementedError"
|
||||
| "OSError"
|
||||
| "OverflowError"
|
||||
| "PendingDeprecationWarning"
|
||||
| "PermissionError"
|
||||
| "ProcessLookupError"
|
||||
| "RecursionError"
|
||||
| "ReferenceError"
|
||||
| "ResourceWarning"
|
||||
| "RuntimeError"
|
||||
| "RuntimeWarning"
|
||||
| "StopAsyncIteration"
|
||||
| "StopIteration"
|
||||
| "SyntaxError"
|
||||
| "SyntaxWarning"
|
||||
| "SystemError"
|
||||
| "SystemExit"
|
||||
| "TabError"
|
||||
| "TimeoutError"
|
||||
| "TypeError"
|
||||
| "UnboundLocalError"
|
||||
| "UnicodeDecodeError"
|
||||
| "UnicodeEncodeError"
|
||||
| "UnicodeError"
|
||||
| "UnicodeTranslateError"
|
||||
| "UnicodeWarning"
|
||||
| "UserWarning"
|
||||
| "ValueError"
|
||||
| "Warning"
|
||||
| "ZeroDivisionError"
|
||||
) && semantic.is_builtin(id)
|
||||
}
|
||||
_ => false,
|
||||
|
||||
@@ -92,6 +92,8 @@ E70.py:71:4: E703 [*] Statement ends with an unnecessary semicolon
|
||||
70 | a = \
|
||||
71 | 5;
|
||||
| ^ E703
|
||||
72 | #:
|
||||
73 | with x(y) as z: ...
|
||||
|
|
||||
= help: Remove unnecessary semicolon
|
||||
|
||||
@@ -101,5 +103,7 @@ E70.py:71:4: E703 [*] Statement ends with an unnecessary semicolon
|
||||
70 70 | a = \
|
||||
71 |- 5;
|
||||
71 |+ 5
|
||||
72 72 | #:
|
||||
73 73 | with x(y) as z: ...
|
||||
|
||||
|
||||
|
||||
@@ -17,144 +17,154 @@ E721.py:5:4: E721 Do not compare types, use `isinstance()`
|
||||
5 | if type(res) != type(""):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
6 | pass
|
||||
7 | #: Okay
|
||||
7 | #: E721
|
||||
|
|
||||
|
||||
E721.py:15:4: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:8:4: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
13 | import types
|
||||
14 |
|
||||
15 | if type(res) is not types.ListType:
|
||||
6 | pass
|
||||
7 | #: E721
|
||||
8 | if type(res) == memoryview:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
9 | pass
|
||||
10 | #: Okay
|
||||
|
|
||||
|
||||
E721.py:18:4: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
16 | import types
|
||||
17 |
|
||||
18 | if type(res) is not types.ListType:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
16 | pass
|
||||
17 | #: E721
|
||||
19 | pass
|
||||
20 | #: E721
|
||||
|
|
||||
|
||||
E721.py:18:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:21:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
16 | pass
|
||||
17 | #: E721
|
||||
18 | assert type(res) == type(False) or type(res) == type(None)
|
||||
19 | pass
|
||||
20 | #: E721
|
||||
21 | assert type(res) == type(False) or type(res) == type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
19 | #: E721
|
||||
20 | assert type(res) == type([])
|
||||
22 | #: E721
|
||||
23 | assert type(res) == type([])
|
||||
|
|
||||
|
||||
E721.py:20:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:23:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
18 | assert type(res) == type(False) or type(res) == type(None)
|
||||
19 | #: E721
|
||||
20 | assert type(res) == type([])
|
||||
21 | assert type(res) == type(False) or type(res) == type(None)
|
||||
22 | #: E721
|
||||
23 | assert type(res) == type([])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
21 | #: E721
|
||||
22 | assert type(res) == type(())
|
||||
24 | #: E721
|
||||
25 | assert type(res) == type(())
|
||||
|
|
||||
|
||||
E721.py:22:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:25:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
20 | assert type(res) == type([])
|
||||
21 | #: E721
|
||||
22 | assert type(res) == type(())
|
||||
23 | assert type(res) == type([])
|
||||
24 | #: E721
|
||||
25 | assert type(res) == type(())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
23 | #: E721
|
||||
24 | assert type(res) == type((0,))
|
||||
26 | #: E721
|
||||
27 | assert type(res) == type((0,))
|
||||
|
|
||||
|
||||
E721.py:24:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:27:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
22 | assert type(res) == type(())
|
||||
23 | #: E721
|
||||
24 | assert type(res) == type((0,))
|
||||
25 | assert type(res) == type(())
|
||||
26 | #: E721
|
||||
27 | assert type(res) == type((0,))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
25 | #: E721
|
||||
26 | assert type(res) == type((0))
|
||||
28 | #: E721
|
||||
29 | assert type(res) == type((0))
|
||||
|
|
||||
|
||||
E721.py:26:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:29:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
24 | assert type(res) == type((0,))
|
||||
25 | #: E721
|
||||
26 | assert type(res) == type((0))
|
||||
27 | assert type(res) == type((0,))
|
||||
28 | #: E721
|
||||
29 | assert type(res) == type((0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
27 | #: E721
|
||||
28 | assert type(res) != type((1, ))
|
||||
30 | #: E721
|
||||
31 | assert type(res) != type((1, ))
|
||||
|
|
||||
|
||||
E721.py:28:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:31:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
26 | assert type(res) == type((0))
|
||||
27 | #: E721
|
||||
28 | assert type(res) != type((1, ))
|
||||
29 | assert type(res) == type((0))
|
||||
30 | #: E721
|
||||
31 | assert type(res) != type((1, ))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
29 | #: Okay
|
||||
30 | assert type(res) is type((1, ))
|
||||
32 | #: Okay
|
||||
33 | assert type(res) is type((1, ))
|
||||
|
|
||||
|
||||
E721.py:30:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:33:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
28 | assert type(res) != type((1, ))
|
||||
29 | #: Okay
|
||||
30 | assert type(res) is type((1, ))
|
||||
31 | assert type(res) != type((1, ))
|
||||
32 | #: Okay
|
||||
33 | assert type(res) is type((1, ))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
31 | #: Okay
|
||||
32 | assert type(res) is not type((1, ))
|
||||
34 | #: Okay
|
||||
35 | assert type(res) is not type((1, ))
|
||||
|
|
||||
|
||||
E721.py:32:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:35:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
30 | assert type(res) is type((1, ))
|
||||
31 | #: Okay
|
||||
32 | assert type(res) is not type((1, ))
|
||||
33 | assert type(res) is type((1, ))
|
||||
34 | #: Okay
|
||||
35 | assert type(res) is not type((1, ))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
33 | #: E211 E721
|
||||
34 | assert type(res) == type ([2, ])
|
||||
36 | #: E211 E721
|
||||
37 | assert type(res) == type ([2, ])
|
||||
|
|
||||
|
||||
E721.py:34:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:37:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
32 | assert type(res) is not type((1, ))
|
||||
33 | #: E211 E721
|
||||
34 | assert type(res) == type ([2, ])
|
||||
35 | assert type(res) is not type((1, ))
|
||||
36 | #: E211 E721
|
||||
37 | assert type(res) == type ([2, ])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
35 | #: E201 E201 E202 E721
|
||||
36 | assert type(res) == type( ( ) )
|
||||
38 | #: E201 E201 E202 E721
|
||||
39 | assert type(res) == type( ( ) )
|
||||
|
|
||||
|
||||
E721.py:36:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:39:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
34 | assert type(res) == type ([2, ])
|
||||
35 | #: E201 E201 E202 E721
|
||||
36 | assert type(res) == type( ( ) )
|
||||
37 | assert type(res) == type ([2, ])
|
||||
38 | #: E201 E201 E202 E721
|
||||
39 | assert type(res) == type( ( ) )
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
37 | #: E201 E202 E721
|
||||
38 | assert type(res) == type( (0, ) )
|
||||
40 | #: E201 E202 E721
|
||||
41 | assert type(res) == type( (0, ) )
|
||||
|
|
||||
|
||||
E721.py:38:8: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:41:8: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
36 | assert type(res) == type( ( ) )
|
||||
37 | #: E201 E202 E721
|
||||
38 | assert type(res) == type( (0, ) )
|
||||
39 | assert type(res) == type( ( ) )
|
||||
40 | #: E201 E202 E721
|
||||
41 | assert type(res) == type( (0, ) )
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
39 | #:
|
||||
42 | #:
|
||||
|
|
||||
|
||||
E721.py:96:12: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
94 | def asdf(self, value: str | None):
|
||||
95 | #: E721
|
||||
96 | if type(value) is str:
|
||||
| ^^^^^^^^^^^^^^^^^^ E721
|
||||
97 | ...
|
||||
|
|
||||
|
||||
E721.py:106:12: E721 Do not compare types, use `isinstance()`
|
||||
E721.py:107:12: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
104 | def asdf(self, value: str | None):
|
||||
105 | #: E721
|
||||
106 | if type(value) is str:
|
||||
105 | def asdf(self, value: str | None):
|
||||
106 | #: E721
|
||||
107 | if type(value) is str:
|
||||
| ^^^^^^^^^^^^^^^^^^ E721
|
||||
107 | ...
|
||||
108 | ...
|
||||
|
|
||||
|
||||
E721.py:117:12: E721 Do not compare types, use `isinstance()`
|
||||
|
|
||||
115 | def asdf(self, value: str | None):
|
||||
116 | #: E721
|
||||
117 | if type(value) is str:
|
||||
| ^^^^^^^^^^^^^^^^^^ E721
|
||||
118 | ...
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -17,96 +17,116 @@ E721.py:5:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()`
|
||||
5 | if type(res) != type(""):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
6 | pass
|
||||
7 | #: Okay
|
||||
7 | #: E721
|
||||
|
|
||||
|
||||
E721.py:18:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:8:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
16 | pass
|
||||
17 | #: E721
|
||||
18 | assert type(res) == type(False) or type(res) == type(None)
|
||||
6 | pass
|
||||
7 | #: E721
|
||||
8 | if type(res) == memoryview:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
9 | pass
|
||||
10 | #: Okay
|
||||
|
|
||||
|
||||
E721.py:21:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
19 | pass
|
||||
20 | #: E721
|
||||
21 | assert type(res) == type(False) or type(res) == type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
19 | #: E721
|
||||
20 | assert type(res) == type([])
|
||||
22 | #: E721
|
||||
23 | assert type(res) == type([])
|
||||
|
|
||||
|
||||
E721.py:20:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:23:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
18 | assert type(res) == type(False) or type(res) == type(None)
|
||||
19 | #: E721
|
||||
20 | assert type(res) == type([])
|
||||
21 | assert type(res) == type(False) or type(res) == type(None)
|
||||
22 | #: E721
|
||||
23 | assert type(res) == type([])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
21 | #: E721
|
||||
22 | assert type(res) == type(())
|
||||
24 | #: E721
|
||||
25 | assert type(res) == type(())
|
||||
|
|
||||
|
||||
E721.py:22:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:25:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
20 | assert type(res) == type([])
|
||||
21 | #: E721
|
||||
22 | assert type(res) == type(())
|
||||
23 | assert type(res) == type([])
|
||||
24 | #: E721
|
||||
25 | assert type(res) == type(())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
23 | #: E721
|
||||
24 | assert type(res) == type((0,))
|
||||
26 | #: E721
|
||||
27 | assert type(res) == type((0,))
|
||||
|
|
||||
|
||||
E721.py:24:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:27:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
22 | assert type(res) == type(())
|
||||
23 | #: E721
|
||||
24 | assert type(res) == type((0,))
|
||||
25 | assert type(res) == type(())
|
||||
26 | #: E721
|
||||
27 | assert type(res) == type((0,))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
25 | #: E721
|
||||
26 | assert type(res) == type((0))
|
||||
28 | #: E721
|
||||
29 | assert type(res) == type((0))
|
||||
|
|
||||
|
||||
E721.py:26:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:29:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
24 | assert type(res) == type((0,))
|
||||
25 | #: E721
|
||||
26 | assert type(res) == type((0))
|
||||
27 | assert type(res) == type((0,))
|
||||
28 | #: E721
|
||||
29 | assert type(res) == type((0))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
27 | #: E721
|
||||
28 | assert type(res) != type((1, ))
|
||||
30 | #: E721
|
||||
31 | assert type(res) != type((1, ))
|
||||
|
|
||||
|
||||
E721.py:28:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:31:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
26 | assert type(res) == type((0))
|
||||
27 | #: E721
|
||||
28 | assert type(res) != type((1, ))
|
||||
29 | assert type(res) == type((0))
|
||||
30 | #: E721
|
||||
31 | assert type(res) != type((1, ))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
29 | #: Okay
|
||||
30 | assert type(res) is type((1, ))
|
||||
32 | #: Okay
|
||||
33 | assert type(res) is type((1, ))
|
||||
|
|
||||
|
||||
E721.py:34:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:37:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
32 | assert type(res) is not type((1, ))
|
||||
33 | #: E211 E721
|
||||
34 | assert type(res) == type ([2, ])
|
||||
35 | assert type(res) is not type((1, ))
|
||||
36 | #: E211 E721
|
||||
37 | assert type(res) == type ([2, ])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
35 | #: E201 E201 E202 E721
|
||||
36 | assert type(res) == type( ( ) )
|
||||
38 | #: E201 E201 E202 E721
|
||||
39 | assert type(res) == type( ( ) )
|
||||
|
|
||||
|
||||
E721.py:36:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:39:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
34 | assert type(res) == type ([2, ])
|
||||
35 | #: E201 E201 E202 E721
|
||||
36 | assert type(res) == type( ( ) )
|
||||
37 | assert type(res) == type ([2, ])
|
||||
38 | #: E201 E201 E202 E721
|
||||
39 | assert type(res) == type( ( ) )
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
37 | #: E201 E202 E721
|
||||
38 | assert type(res) == type( (0, ) )
|
||||
40 | #: E201 E202 E721
|
||||
41 | assert type(res) == type( (0, ) )
|
||||
|
|
||||
|
||||
E721.py:38:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
E721.py:41:8: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
36 | assert type(res) == type( ( ) )
|
||||
37 | #: E201 E202 E721
|
||||
38 | assert type(res) == type( (0, ) )
|
||||
39 | assert type(res) == type( ( ) )
|
||||
40 | #: E201 E202 E721
|
||||
41 | assert type(res) == type( (0, ) )
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ E721
|
||||
39 | #:
|
||||
42 | #:
|
||||
|
|
||||
|
||||
E721.py:59:4: E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
|
||||
|
|
||||
57 | pass
|
||||
58 | #: E721
|
||||
59 | if type(res) == type:
|
||||
| ^^^^^^^^^^^^^^^^^ E721
|
||||
60 | pass
|
||||
61 | #: Okay
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use memchr::memchr_iter;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -46,14 +46,16 @@ use crate::docstrings::Docstring;
|
||||
#[violation]
|
||||
pub struct EscapeSequenceInDocstring;
|
||||
|
||||
impl AlwaysFixableViolation for EscapeSequenceInDocstring {
|
||||
impl Violation for EscapeSequenceInDocstring {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(r#"Use `r"""` if any backslashes in a docstring"#)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!(r#"Add `r` prefix"#)
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some(format!(r#"Add `r` prefix"#))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +76,12 @@ pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
|
||||
}) {
|
||||
let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range());
|
||||
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
"r".to_owned() + docstring.contents,
|
||||
docstring.range(),
|
||||
)));
|
||||
if !docstring.leading_quote().contains(['u', 'U']) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
"r".to_owned() + docstring.contents,
|
||||
docstring.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -17,4 +17,12 @@ D301.py:2:5: D301 [*] Use `r"""` if any backslashes in a docstring
|
||||
4 4 |
|
||||
5 5 | def double_quotes_backslash_raw():
|
||||
|
||||
D301.py:37:5: D301 Use `r"""` if any backslashes in a docstring
|
||||
|
|
||||
36 | def shouldnt_add_raw_here2():
|
||||
37 | u"Sum\\mary."
|
||||
| ^^^^^^^^^^^^^ D301
|
||||
|
|
||||
= help: Add `r` prefix
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ mod tests {
|
||||
use crate::linter::{check_path, LinterResult};
|
||||
use crate::registry::{AsRule, Linter, Rule};
|
||||
use crate::rules::pyflakes;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::settings::{flags, LinterSettings};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::test::{test_path, test_snippet};
|
||||
@@ -145,6 +146,7 @@ mod tests {
|
||||
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"))]
|
||||
#[test_case(Rule::UnusedVariable, Path::new("F841_2.py"))]
|
||||
#[test_case(Rule::UnusedVariable, Path::new("F841_3.py"))]
|
||||
#[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))]
|
||||
#[test_case(Rule::UnusedAnnotation, Path::new("F842.py"))]
|
||||
#[test_case(Rule::RaiseNotImplemented, Path::new("F901.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
@@ -157,6 +159,24 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UnusedVariable, Path::new("F841_4.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("pyflakes").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f841_dummy_variable_rgx() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
@@ -1126,7 +1146,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn used_as_star_unpack() {
|
||||
// Star names in unpack are used if RHS is not a tuple/list literal.
|
||||
// In stable, starred names in unpack are used if RHS is not a tuple/list literal.
|
||||
// In preview, these should be marked as unused.
|
||||
flakes(
|
||||
r#"
|
||||
def f():
|
||||
|
||||
@@ -12,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::delete_stmt;
|
||||
use crate::settings::types::PreviewMode;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of unused variables in function scopes.
|
||||
@@ -24,6 +25,9 @@ use crate::fix::edits::delete_stmt;
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// Under [preview mode](https://docs.astral.sh/ruff/preview), this rule also
|
||||
/// triggers on unused unpacked assignments (for example, `x, y = foo()`).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo():
|
||||
@@ -318,7 +322,10 @@ pub(crate) fn unused_variable(checker: &Checker, scope: &Scope, diagnostics: &mu
|
||||
.bindings()
|
||||
.map(|(name, binding_id)| (name, checker.semantic().binding(binding_id)))
|
||||
.filter_map(|(name, binding)| {
|
||||
if (binding.kind.is_assignment() || binding.kind.is_named_expr_assignment())
|
||||
if (binding.kind.is_assignment()
|
||||
|| binding.kind.is_named_expr_assignment()
|
||||
|| (matches!(checker.settings.preview, PreviewMode::Enabled)
|
||||
&& binding.kind.is_unpacked_assignment()))
|
||||
&& !binding.is_nonlocal()
|
||||
&& !binding.is_global()
|
||||
&& !binding.is_used()
|
||||
|
||||
@@ -20,7 +20,7 @@ F841_1.py:6:8: F841 Local variable `y` is assigned to but never used
|
||||
F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
|
||||
|
|
||||
15 | def f():
|
||||
16 | (x, y) = coords = 1, 2 # this triggers F841 on coords
|
||||
16 | (x, y) = coords = 1, 2
|
||||
| ^^^^^^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `coords`
|
||||
@@ -29,8 +29,8 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 15 | def f():
|
||||
16 |- (x, y) = coords = 1, 2 # this triggers F841 on coords
|
||||
16 |+ (x, y) = 1, 2 # this triggers F841 on coords
|
||||
16 |- (x, y) = coords = 1, 2
|
||||
16 |+ (x, y) = 1, 2
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 19 | def f():
|
||||
@@ -38,7 +38,7 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used
|
||||
F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used
|
||||
|
|
||||
19 | def f():
|
||||
20 | coords = (x, y) = 1, 2 # this triggers F841 on coords
|
||||
20 | coords = (x, y) = 1, 2
|
||||
| ^^^^^^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `coords`
|
||||
@@ -47,8 +47,8 @@ F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 19 | def f():
|
||||
20 |- coords = (x, y) = 1, 2 # this triggers F841 on coords
|
||||
20 |+ (x, y) = 1, 2 # this triggers F841 on coords
|
||||
20 |- coords = (x, y) = 1, 2
|
||||
20 |+ (x, y) = 1, 2
|
||||
21 21 |
|
||||
22 22 |
|
||||
23 23 | def f():
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used
|
||||
|
|
||||
11 | def bar():
|
||||
12 | a = foo()
|
||||
| ^ F841
|
||||
13 | b, c = foo()
|
||||
|
|
||||
= help: Remove assignment to unused variable `a`
|
||||
|
||||
ℹ Suggested fix
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 11 | def bar():
|
||||
12 |- a = foo()
|
||||
12 |+ foo()
|
||||
13 13 | b, c = foo()
|
||||
14 14 |
|
||||
15 15 |
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used
|
||||
|
|
||||
11 | def bar():
|
||||
12 | a = foo()
|
||||
| ^ F841
|
||||
13 | b, c = foo()
|
||||
|
|
||||
= help: Remove assignment to unused variable `a`
|
||||
|
||||
ℹ Suggested fix
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 11 | def bar():
|
||||
12 |- a = foo()
|
||||
12 |+ foo()
|
||||
13 13 | b, c = foo()
|
||||
14 14 |
|
||||
15 15 |
|
||||
|
||||
F841_4.py:13:5: F841 Local variable `b` is assigned to but never used
|
||||
|
|
||||
11 | def bar():
|
||||
12 | a = foo()
|
||||
13 | b, c = foo()
|
||||
| ^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `b`
|
||||
|
||||
F841_4.py:13:8: F841 Local variable `c` is assigned to but never used
|
||||
|
|
||||
11 | def bar():
|
||||
12 | a = foo()
|
||||
13 | b, c = foo()
|
||||
| ^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `c`
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
|
||||
stmt.range(),
|
||||
);
|
||||
|
||||
// The fix is only safe in a type stub because new-style aliases have different runtime behavior
|
||||
// The fix is only safe in a type stub because new-style aliases have different runtime behavior
|
||||
// See https://github.com/astral-sh/ruff/issues/6434
|
||||
let fix = if checker.source_type.is_stub() {
|
||||
Fix::safe_edit(edit)
|
||||
|
||||
@@ -34,3 +34,30 @@ pub(super) fn generate_method_call(name: &str, method: &str, generator: Generato
|
||||
};
|
||||
generator.stmt(&stmt.into())
|
||||
}
|
||||
|
||||
/// Format a code snippet comparing `name` to `None` (e.g., `name is None`).
|
||||
pub(super) fn generate_none_identity_comparison(
|
||||
name: &str,
|
||||
negate: bool,
|
||||
generator: Generator,
|
||||
) -> String {
|
||||
// Construct `name`.
|
||||
let var = ast::ExprName {
|
||||
id: name.to_string(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
};
|
||||
// Construct `name is None` or `name is not None`.
|
||||
let op = if negate {
|
||||
ast::CmpOp::IsNot
|
||||
} else {
|
||||
ast::CmpOp::Is
|
||||
};
|
||||
let compare = ast::ExprCompare {
|
||||
left: Box::new(var.into()),
|
||||
ops: vec![op],
|
||||
comparators: vec![ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())],
|
||||
range: TextRange::default(),
|
||||
};
|
||||
generator.expr(&compare.into())
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ mod tests {
|
||||
#[test_case(Rule::ImplicitCwd, Path::new("FURB177.py"))]
|
||||
#[test_case(Rule::SingleItemMembershipTest, Path::new("FURB171.py"))]
|
||||
#[test_case(Rule::IsinstanceTypeNone, Path::new("FURB168.py"))]
|
||||
#[test_case(Rule::TypeNoneComparison, Path::new("FURB169.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -97,9 +97,9 @@ pub(crate) fn check_and_remove_from_set(checker: &mut Checker, if_stmt: &ast::St
|
||||
// Check if what we assume is set is indeed a set.
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_name(check_set)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.map_or(false, |binding| is_set(binding, checker.semantic()))
|
||||
.only_binding(check_set)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
.is_some_and(|binding| is_set(binding, checker.semantic()))
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::analyze::typing::{is_dict, is_list};
|
||||
use ruff_python_semantic::{Binding, SemanticModel};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
|
||||
|
||||
// Fix is only supported for single-target deletions.
|
||||
if delete.targets.len() == 1 {
|
||||
let replacement = generate_method_call(name, "clear", checker.generator());
|
||||
let replacement = generate_method_call(&name.id, "clear", checker.generator());
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::replacement(
|
||||
replacement,
|
||||
delete.start(),
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
|
||||
}
|
||||
|
||||
/// Match `del expr[:]` where `expr` is a list or a dict.
|
||||
fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a str> {
|
||||
fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a ast::ExprName> {
|
||||
// Check that it is `del expr[...]`.
|
||||
let subscript = expr.as_subscript_expr()?;
|
||||
|
||||
@@ -100,22 +100,9 @@ fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check that it is del var[:]
|
||||
let ast::ExprName { id: name, .. } = subscript.value.as_name_expr()?;
|
||||
|
||||
// Let's find definition for var
|
||||
let scope = semantic.current_scope();
|
||||
let bindings: Vec<&Binding> = scope
|
||||
.get_all(name)
|
||||
.map(|binding_id| semantic.binding(binding_id))
|
||||
.collect();
|
||||
|
||||
// NOTE: Maybe it is too strict of a limitation, but it seems reasonable.
|
||||
let [binding] = bindings.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// It should only apply to variables that are known to be lists or dicts.
|
||||
let name = subscript.value.as_name_expr()?;
|
||||
let binding = semantic.binding(semantic.only_binding(name)?);
|
||||
if !(is_dict(binding, semantic) || is_list(binding, semantic)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::pad;
|
||||
use crate::rules::refurb::helpers::generate_none_identity_comparison;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `isinstance` that check if an object is of type `None`.
|
||||
@@ -69,7 +69,8 @@ pub(crate) fn isinstance_type_none(checker: &mut Checker, call: &ast::ExprCall)
|
||||
return;
|
||||
};
|
||||
let mut diagnostic = Diagnostic::new(IsinstanceTypeNone, call.range());
|
||||
let replacement = generate_replacement(object_name, checker.generator());
|
||||
let replacement =
|
||||
generate_none_identity_comparison(object_name, false, checker.generator());
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
pad(replacement, call.range(), checker.locator()),
|
||||
call.range(),
|
||||
@@ -117,21 +118,3 @@ fn is_none(expr: &Expr) -> bool {
|
||||
}
|
||||
inner(expr, false)
|
||||
}
|
||||
|
||||
/// Format a code snippet comparing `name` to `None` (e.g., `name is None`).
|
||||
fn generate_replacement(name: &str, generator: Generator) -> String {
|
||||
// Construct `name`.
|
||||
let var = ast::ExprName {
|
||||
id: name.to_string(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
};
|
||||
// Construct `name is None`.
|
||||
let compare = ast::ExprCompare {
|
||||
left: Box::new(var.into()),
|
||||
ops: vec![ast::CmpOp::Is],
|
||||
comparators: vec![ast::Expr::NoneLiteral(ast::ExprNoneLiteral::default())],
|
||||
range: TextRange::default(),
|
||||
};
|
||||
generator.expr(&compare.into())
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ pub(crate) use reimplemented_starmap::*;
|
||||
pub(crate) use repeated_append::*;
|
||||
pub(crate) use single_item_membership_test::*;
|
||||
pub(crate) use slice_copy::*;
|
||||
pub(crate) use type_none_comparison::*;
|
||||
pub(crate) use unnecessary_enumerate::*;
|
||||
|
||||
mod check_and_remove_from_set;
|
||||
@@ -20,4 +21,5 @@ mod reimplemented_starmap;
|
||||
mod repeated_append;
|
||||
mod single_item_membership_test;
|
||||
mod slice_copy;
|
||||
mod type_none_comparison;
|
||||
mod unnecessary_enumerate;
|
||||
|
||||
@@ -76,7 +76,7 @@ impl Violation for RepeatedAppend {
|
||||
|
||||
/// FURB113
|
||||
pub(crate) fn repeated_append(checker: &mut Checker, stmt: &Stmt) {
|
||||
let Some(appends) = match_consecutive_appends(checker.semantic(), stmt) else {
|
||||
let Some(appends) = match_consecutive_appends(stmt, checker.semantic()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -163,8 +163,8 @@ impl Ranged for AppendGroup<'_> {
|
||||
|
||||
/// Match consecutive calls to `append` on list variables starting from the given statement.
|
||||
fn match_consecutive_appends<'a>(
|
||||
semantic: &'a SemanticModel,
|
||||
stmt: &'a Stmt,
|
||||
semantic: &'a SemanticModel,
|
||||
) -> Option<Vec<Append<'a>>> {
|
||||
// Match the current statement, to see if it's an append.
|
||||
let append = match_append(semantic, stmt)?;
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::pad;
|
||||
use crate::rules::refurb::helpers::generate_none_identity_comparison;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `type` that compare the type of an object to the type of
|
||||
/// `None`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// There is only ever one instance of `None`, so it is more efficient and
|
||||
/// readable to use the `is` operator to check if an object is `None`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// type(obj) is type(None)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// obj is None
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `isinstance`](https://docs.python.org/3/library/functions.html#isinstance)
|
||||
/// - [Python documentation: `None`](https://docs.python.org/3/library/constants.html#None)
|
||||
/// - [Python documentation: `type`](https://docs.python.org/3/library/functions.html#type)
|
||||
/// - [Python documentation: Identity comparisons](https://docs.python.org/3/reference/expressions.html#is-not)
|
||||
#[violation]
|
||||
pub struct TypeNoneComparison {
|
||||
object: String,
|
||||
comparison: Comparison,
|
||||
}
|
||||
|
||||
impl Violation for TypeNoneComparison {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TypeNoneComparison { object, .. } = self;
|
||||
format!("Compare the identities of `{object}` and `None` instead of their respective types")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let TypeNoneComparison { object, comparison } = self;
|
||||
match comparison {
|
||||
Comparison::Is | Comparison::Eq => Some(format!("Replace with `{object} is None`")),
|
||||
Comparison::IsNot | Comparison::NotEq => {
|
||||
Some(format!("Replace with `{object} is not None`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FURB169
|
||||
pub(crate) fn type_none_comparison(checker: &mut Checker, compare: &ast::ExprCompare) {
|
||||
let ([op], [right]) = (compare.ops.as_slice(), compare.comparators.as_slice()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Ensure that the comparison is an identity or equality test.
|
||||
let comparison = match op {
|
||||
CmpOp::Is => Comparison::Is,
|
||||
CmpOp::IsNot => Comparison::IsNot,
|
||||
CmpOp::Eq => Comparison::Eq,
|
||||
CmpOp::NotEq => Comparison::NotEq,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Get the objects whose types are being compared.
|
||||
let Some(left_arg) = type_call_arg(&compare.left, checker.semantic()) else {
|
||||
return;
|
||||
};
|
||||
let Some(right_arg) = type_call_arg(right, checker.semantic()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If one of the objects is `None`, get the other object; else, return.
|
||||
let other_arg = match (
|
||||
left_arg.is_none_literal_expr(),
|
||||
right_arg.is_none_literal_expr(),
|
||||
) {
|
||||
(true, false) => right_arg,
|
||||
(false, true) => left_arg,
|
||||
// If both are `None`, just pick one.
|
||||
(true, true) => left_arg,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Get the name of the other object (or `None` if both were `None`).
|
||||
let other_arg_name = match other_arg {
|
||||
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||
Expr::NoneLiteral { .. } => "None",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
TypeNoneComparison {
|
||||
object: other_arg_name.to_string(),
|
||||
comparison,
|
||||
},
|
||||
compare.range(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
pad(
|
||||
match comparison {
|
||||
Comparison::Is | Comparison::Eq => {
|
||||
generate_none_identity_comparison(other_arg_name, false, checker.generator())
|
||||
}
|
||||
Comparison::IsNot | Comparison::NotEq => {
|
||||
generate_none_identity_comparison(other_arg_name, true, checker.generator())
|
||||
}
|
||||
},
|
||||
compare.range(),
|
||||
checker.locator(),
|
||||
),
|
||||
compare.range(),
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
/// Returns the object passed to the function, if the expression is a call to
|
||||
/// `type` with a single argument.
|
||||
fn type_call_arg<'a>(expr: &'a Expr, semantic: &'a SemanticModel) -> Option<&'a Expr> {
|
||||
// The expression must be a single-argument call to `type`.
|
||||
let ast::ExprCall {
|
||||
func, arguments, ..
|
||||
} = expr.as_call_expr()?;
|
||||
if arguments.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The function itself must be the builtin `type`.
|
||||
let ast::ExprName { id, .. } = func.as_name_expr()?;
|
||||
if id.as_str() != "type" || !semantic.is_builtin(id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
arguments.find_positional(0)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Comparison {
|
||||
Is,
|
||||
IsNot,
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Arguments, Expr, Int};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_semantic::analyze::typing::{is_dict, is_list, is_set, is_tuple};
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -114,7 +114,7 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
|
||||
};
|
||||
|
||||
// Get the first argument, which is the sequence to iterate over.
|
||||
let Some(Expr::Name(ast::ExprName { id: sequence, .. })) = arguments.args.first() else {
|
||||
let Some(Expr::Name(sequence)) = arguments.args.first() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -138,7 +138,8 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
|
||||
);
|
||||
|
||||
// The index is unused, so replace with `for value in sequence`.
|
||||
let replace_iter = Edit::range_replacement(sequence.into(), stmt_for.iter.range());
|
||||
let replace_iter =
|
||||
Edit::range_replacement(sequence.id.to_string(), stmt_for.iter.range());
|
||||
let replace_target = Edit::range_replacement(
|
||||
pad(
|
||||
checker.locator().slice(value).to_string(),
|
||||
@@ -154,12 +155,11 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
|
||||
(false, true) => {
|
||||
// Ensure the sequence object works with `len`. If it doesn't, the
|
||||
// fix is unclear.
|
||||
let scope = checker.semantic().current_scope();
|
||||
let bindings: Vec<&Binding> = scope
|
||||
.get_all(sequence)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.collect();
|
||||
let [binding] = bindings.as_slice() else {
|
||||
let Some(binding) = checker
|
||||
.semantic()
|
||||
.only_binding(sequence)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// This will lead to a lot of false negatives, but it is the best
|
||||
@@ -193,7 +193,7 @@ pub(crate) fn unnecessary_enumerate(checker: &mut Checker, stmt_for: &ast::StmtF
|
||||
)
|
||||
}) {
|
||||
let replace_iter = Edit::range_replacement(
|
||||
generate_range_len_call(sequence, checker.generator()),
|
||||
generate_range_len_call(&sequence.id, checker.generator()),
|
||||
stmt_for.iter.range(),
|
||||
);
|
||||
|
||||
|
||||
@@ -127,6 +127,27 @@ FURB131.py:43:5: FURB131 [*] Prefer `clear` over deleting a full slice
|
||||
43 |+ x.clear()
|
||||
44 44 |
|
||||
45 45 |
|
||||
46 46 | # these should not
|
||||
46 46 | def yes_five(x: Dict[int, str]):
|
||||
|
||||
FURB131.py:48:5: FURB131 [*] Prefer `clear` over deleting a full slice
|
||||
|
|
||||
46 | def yes_five(x: Dict[int, str]):
|
||||
47 | # FURB131
|
||||
48 | del x[:]
|
||||
| ^^^^^^^^ FURB131
|
||||
49 |
|
||||
50 | x = 1
|
||||
|
|
||||
= help: Replace with `clear()`
|
||||
|
||||
ℹ Suggested fix
|
||||
45 45 |
|
||||
46 46 | def yes_five(x: Dict[int, str]):
|
||||
47 47 | # FURB131
|
||||
48 |- del x[:]
|
||||
48 |+ x.clear()
|
||||
49 49 |
|
||||
50 50 | x = 1
|
||||
51 51 |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/refurb/mod.rs
|
||||
---
|
||||
FURB169.py:5:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
3 | # Error.
|
||||
4 |
|
||||
5 | type(foo) is type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
6 |
|
||||
7 | type(None) is type(foo)
|
||||
|
|
||||
= help: Replace with `foo is None`
|
||||
|
||||
ℹ Fix
|
||||
2 2 |
|
||||
3 3 | # Error.
|
||||
4 4 |
|
||||
5 |-type(foo) is type(None)
|
||||
5 |+foo is None
|
||||
6 6 |
|
||||
7 7 | type(None) is type(foo)
|
||||
8 8 |
|
||||
|
||||
FURB169.py:7:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
5 | type(foo) is type(None)
|
||||
6 |
|
||||
7 | type(None) is type(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
8 |
|
||||
9 | type(None) is type(None)
|
||||
|
|
||||
= help: Replace with `foo is None`
|
||||
|
||||
ℹ Fix
|
||||
4 4 |
|
||||
5 5 | type(foo) is type(None)
|
||||
6 6 |
|
||||
7 |-type(None) is type(foo)
|
||||
7 |+foo is None
|
||||
8 8 |
|
||||
9 9 | type(None) is type(None)
|
||||
10 10 |
|
||||
|
||||
FURB169.py:9:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
||||
|
|
||||
7 | type(None) is type(foo)
|
||||
8 |
|
||||
9 | type(None) is type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
10 |
|
||||
11 | type(foo) is not type(None)
|
||||
|
|
||||
= help: Replace with `None is None`
|
||||
|
||||
ℹ Fix
|
||||
6 6 |
|
||||
7 7 | type(None) is type(foo)
|
||||
8 8 |
|
||||
9 |-type(None) is type(None)
|
||||
9 |+None is None
|
||||
10 10 |
|
||||
11 11 | type(foo) is not type(None)
|
||||
12 12 |
|
||||
|
||||
FURB169.py:11:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
9 | type(None) is type(None)
|
||||
10 |
|
||||
11 | type(foo) is not type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
12 |
|
||||
13 | type(None) is not type(foo)
|
||||
|
|
||||
= help: Replace with `foo is not None`
|
||||
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
9 9 | type(None) is type(None)
|
||||
10 10 |
|
||||
11 |-type(foo) is not type(None)
|
||||
11 |+foo is not None
|
||||
12 12 |
|
||||
13 13 | type(None) is not type(foo)
|
||||
14 14 |
|
||||
|
||||
FURB169.py:13:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
11 | type(foo) is not type(None)
|
||||
12 |
|
||||
13 | type(None) is not type(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
14 |
|
||||
15 | type(None) is not type(None)
|
||||
|
|
||||
= help: Replace with `foo is not None`
|
||||
|
||||
ℹ Fix
|
||||
10 10 |
|
||||
11 11 | type(foo) is not type(None)
|
||||
12 12 |
|
||||
13 |-type(None) is not type(foo)
|
||||
13 |+foo is not None
|
||||
14 14 |
|
||||
15 15 | type(None) is not type(None)
|
||||
16 16 |
|
||||
|
||||
FURB169.py:15:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
||||
|
|
||||
13 | type(None) is not type(foo)
|
||||
14 |
|
||||
15 | type(None) is not type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
16 |
|
||||
17 | type(foo) == type(None)
|
||||
|
|
||||
= help: Replace with `None is not None`
|
||||
|
||||
ℹ Fix
|
||||
12 12 |
|
||||
13 13 | type(None) is not type(foo)
|
||||
14 14 |
|
||||
15 |-type(None) is not type(None)
|
||||
15 |+None is not None
|
||||
16 16 |
|
||||
17 17 | type(foo) == type(None)
|
||||
18 18 |
|
||||
|
||||
FURB169.py:17:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
15 | type(None) is not type(None)
|
||||
16 |
|
||||
17 | type(foo) == type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
18 |
|
||||
19 | type(None) == type(foo)
|
||||
|
|
||||
= help: Replace with `foo is None`
|
||||
|
||||
ℹ Fix
|
||||
14 14 |
|
||||
15 15 | type(None) is not type(None)
|
||||
16 16 |
|
||||
17 |-type(foo) == type(None)
|
||||
17 |+foo is None
|
||||
18 18 |
|
||||
19 19 | type(None) == type(foo)
|
||||
20 20 |
|
||||
|
||||
FURB169.py:19:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
17 | type(foo) == type(None)
|
||||
18 |
|
||||
19 | type(None) == type(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
20 |
|
||||
21 | type(None) == type(None)
|
||||
|
|
||||
= help: Replace with `foo is None`
|
||||
|
||||
ℹ Fix
|
||||
16 16 |
|
||||
17 17 | type(foo) == type(None)
|
||||
18 18 |
|
||||
19 |-type(None) == type(foo)
|
||||
19 |+foo is None
|
||||
20 20 |
|
||||
21 21 | type(None) == type(None)
|
||||
22 22 |
|
||||
|
||||
FURB169.py:21:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
||||
|
|
||||
19 | type(None) == type(foo)
|
||||
20 |
|
||||
21 | type(None) == type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
22 |
|
||||
23 | type(foo) != type(None)
|
||||
|
|
||||
= help: Replace with `None is None`
|
||||
|
||||
ℹ Fix
|
||||
18 18 |
|
||||
19 19 | type(None) == type(foo)
|
||||
20 20 |
|
||||
21 |-type(None) == type(None)
|
||||
21 |+None is None
|
||||
22 22 |
|
||||
23 23 | type(foo) != type(None)
|
||||
24 24 |
|
||||
|
||||
FURB169.py:23:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
21 | type(None) == type(None)
|
||||
22 |
|
||||
23 | type(foo) != type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
24 |
|
||||
25 | type(None) != type(foo)
|
||||
|
|
||||
= help: Replace with `foo is not None`
|
||||
|
||||
ℹ Fix
|
||||
20 20 |
|
||||
21 21 | type(None) == type(None)
|
||||
22 22 |
|
||||
23 |-type(foo) != type(None)
|
||||
23 |+foo is not None
|
||||
24 24 |
|
||||
25 25 | type(None) != type(foo)
|
||||
26 26 |
|
||||
|
||||
FURB169.py:25:1: FURB169 [*] Compare the identities of `foo` and `None` instead of their respective types
|
||||
|
|
||||
23 | type(foo) != type(None)
|
||||
24 |
|
||||
25 | type(None) != type(foo)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
26 |
|
||||
27 | type(None) != type(None)
|
||||
|
|
||||
= help: Replace with `foo is not None`
|
||||
|
||||
ℹ Fix
|
||||
22 22 |
|
||||
23 23 | type(foo) != type(None)
|
||||
24 24 |
|
||||
25 |-type(None) != type(foo)
|
||||
25 |+foo is not None
|
||||
26 26 |
|
||||
27 27 | type(None) != type(None)
|
||||
28 28 |
|
||||
|
||||
FURB169.py:27:1: FURB169 [*] Compare the identities of `None` and `None` instead of their respective types
|
||||
|
|
||||
25 | type(None) != type(foo)
|
||||
26 |
|
||||
27 | type(None) != type(None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB169
|
||||
28 |
|
||||
29 | # Ok.
|
||||
|
|
||||
= help: Replace with `None is not None`
|
||||
|
||||
ℹ Fix
|
||||
24 24 |
|
||||
25 25 | type(None) != type(foo)
|
||||
26 26 |
|
||||
27 |-type(None) != type(None)
|
||||
27 |+None is not None
|
||||
28 28 |
|
||||
29 29 | # Ok.
|
||||
30 30 |
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::importer::ImportRequest;
|
||||
/// lists:
|
||||
///
|
||||
/// - `functools.reduce(operator.iconcat, lists, [])`
|
||||
/// - `list(itertools.chain.from_iterable(lists)`
|
||||
/// - `list(itertools.chain.from_iterable(lists))`
|
||||
/// - `[item for sublist in lists for item in sublist]`
|
||||
///
|
||||
/// ## Example
|
||||
|
||||
@@ -20,15 +20,6 @@ use crate::fix::snippet::SourceCodeSnippet;
|
||||
/// element of the collection, you can use `next(...)` or `next(iter(...)` to
|
||||
/// lazily fetch the first element.
|
||||
///
|
||||
/// Note that migrating from `list(...)[0]` to `next(iter(...))` can change
|
||||
/// the behavior of your program in two ways:
|
||||
///
|
||||
/// 1. First, `list(...)` will eagerly evaluate the entire collection, while
|
||||
/// `next(iter(...))` will only evaluate the first element. As such, any
|
||||
/// side effects that occur during iteration will be delayed.
|
||||
/// 2. Second, `list(...)[0]` will raise `IndexError` if the collection is
|
||||
/// empty, while `next(iter(...))` will raise `StopIteration`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// head = list(x)[0]
|
||||
@@ -41,6 +32,16 @@ use crate::fix::snippet::SourceCodeSnippet;
|
||||
/// head = next(x * x for x in range(10))
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as migrating from `list(...)[0]` to
|
||||
/// `next(iter(...))` can change the behavior of your program in two ways:
|
||||
///
|
||||
/// 1. First, `list(...)` will eagerly evaluate the entire collection, while
|
||||
/// `next(iter(...))` will only evaluate the first element. As such, any
|
||||
/// side effects that occur during iteration will be delayed.
|
||||
/// 2. Second, `list(...)[0]` will raise `IndexError` if the collection is
|
||||
/// empty, while `next(iter(...))` will raise `StopIteration`.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Iterators and Iterables in Python: Run Efficient Iterations](https://realpython.com/python-iterators-iterables/#when-to-use-an-iterator-in-python)
|
||||
#[violation]
|
||||
|
||||
@@ -31,6 +31,10 @@ use crate::checkers::ast::Checker;
|
||||
/// except ValueError:
|
||||
/// raise
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as it doesn't properly handle bound
|
||||
/// exceptions that are shadowed between the `except` and `raise` statements.
|
||||
#[violation]
|
||||
pub struct VerboseRaise;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use crate::call_path::CallPath;
|
||||
use crate::parenthesize::parenthesized_range;
|
||||
use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
|
||||
use crate::visitor::Visitor;
|
||||
use crate::AnyNodeRef;
|
||||
use crate::{
|
||||
self as ast, Arguments, CmpOp, ExceptHandler, Expr, MatchCase, Pattern, Stmt, TypeParam,
|
||||
@@ -931,6 +932,29 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Visitor`] that detects the presence of `await` expressions in the current scope.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AwaitVisitor {
|
||||
pub seen_await: bool,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for AwaitVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
|
||||
_ => crate::visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
if let Expr::Await(ast::ExprAwait { .. }) = expr {
|
||||
self.seen_await = true;
|
||||
} else {
|
||||
crate::visitor::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if a `Stmt` is a docstring.
|
||||
pub fn is_docstring_stmt(stmt: &Stmt) -> bool {
|
||||
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
|
||||
|
||||
@@ -203,3 +203,28 @@ lambda: ( # comment
|
||||
y:
|
||||
z
|
||||
)
|
||||
|
||||
lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/8179
|
||||
def a():
|
||||
return b(
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
|
||||
*args, **kwargs
|
||||
),
|
||||
)
|
||||
|
||||
def a():
|
||||
return b(
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f=lambda self, araa, kkkwargs,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
args,kwargs,
|
||||
e=1, f=2, g=2: d,
|
||||
g = 10
|
||||
)
|
||||
|
||||
|
||||
@@ -22,8 +22,6 @@ def foo():
|
||||
pass
|
||||
|
||||
|
||||
(yield a, b) = (1, 2)
|
||||
|
||||
# some comment
|
||||
for e in l : yield e # some comment
|
||||
|
||||
|
||||
@@ -102,7 +102,15 @@ impl FormatNodeRule<Parameters> for FormatParameters {
|
||||
dangling.split_at(parenthesis_comments_end);
|
||||
|
||||
let format_inner = format_with(|f: &mut PyFormatter| {
|
||||
let separator = format_with(|f| write!(f, [token(","), soft_line_break_or_space()]));
|
||||
let separator = format_with(|f: &mut PyFormatter| {
|
||||
token(",").fmt(f)?;
|
||||
|
||||
if f.context().node_level().is_parenthesized() {
|
||||
soft_line_break_or_space().fmt(f)
|
||||
} else {
|
||||
space().fmt(f)
|
||||
}
|
||||
});
|
||||
let mut joiner = f.join_with(separator);
|
||||
let mut last_node: Option<AnyNodeRef> = None;
|
||||
|
||||
@@ -232,8 +240,6 @@ impl FormatNodeRule<Parameters> for FormatParameters {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
|
||||
|
||||
let num_parameters = posonlyargs.len()
|
||||
+ args.len()
|
||||
+ usize::from(vararg.is_some())
|
||||
@@ -243,12 +249,14 @@ impl FormatNodeRule<Parameters> for FormatParameters {
|
||||
if self.parentheses == ParametersParentheses::Never {
|
||||
write!(f, [group(&format_inner), dangling_comments(dangling)])
|
||||
} else if num_parameters == 0 {
|
||||
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
|
||||
// No parameters, format any dangling comments between `()`
|
||||
write!(f, [empty_parenthesized("(", dangling, ")")])
|
||||
} else {
|
||||
// Intentionally avoid `parenthesized`, which groups the entire formatted contents.
|
||||
// We want parameters to be grouped alongside return types, one level up, so we
|
||||
// format them "inline" here.
|
||||
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
|
||||
@@ -209,6 +209,31 @@ lambda: ( # comment
|
||||
y:
|
||||
z
|
||||
)
|
||||
|
||||
lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/8179
|
||||
def a():
|
||||
return b(
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
|
||||
*args, **kwargs
|
||||
),
|
||||
)
|
||||
|
||||
def a():
|
||||
return b(
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f=lambda self, araa, kkkwargs,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
args,kwargs,
|
||||
e=1, f=2, g=2: d,
|
||||
g = 10
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -413,6 +438,40 @@ lambda: ( # comment
|
||||
# comment
|
||||
y: z
|
||||
)
|
||||
|
||||
lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
|
||||
*args, **kwargs
|
||||
), e=1, f=2, g=2: d
|
||||
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/8179
|
||||
def a():
|
||||
return b(
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f=lambda self,
|
||||
*args,
|
||||
**kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs),
|
||||
)
|
||||
|
||||
|
||||
def a():
|
||||
return b(
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f=lambda self,
|
||||
araa,
|
||||
kkkwargs,
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
args,
|
||||
kwargs,
|
||||
e=1,
|
||||
f=2,
|
||||
g=2: d,
|
||||
g=10,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -28,8 +28,6 @@ def foo():
|
||||
pass
|
||||
|
||||
|
||||
(yield a, b) = (1, 2)
|
||||
|
||||
# some comment
|
||||
for e in l : yield e # some comment
|
||||
|
||||
@@ -151,8 +149,6 @@ def foo():
|
||||
# comment
|
||||
pass
|
||||
|
||||
(yield a, b) = (1, 2)
|
||||
|
||||
# some comment
|
||||
for e in l:
|
||||
yield e # some comment
|
||||
|
||||
733
crates/ruff_python_parser/src/invalid.rs
Normal file
733
crates/ruff_python_parser/src/invalid.rs
Normal file
@@ -0,0 +1,733 @@
|
||||
/*!
|
||||
Defines some helper routines for rejecting invalid Python programs.
|
||||
|
||||
These routines are named in a way that supports qualified use. For example,
|
||||
`invalid::assignment_targets`.
|
||||
*/
|
||||
|
||||
use {ruff_python_ast::Expr, ruff_text_size::TextSize};
|
||||
|
||||
use crate::lexer::{LexicalError, LexicalErrorType};
|
||||
|
||||
/// Returns an error for invalid assignment targets.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This returns an error when any of the given expressions are themselves
|
||||
/// or contain an expression that is invalid on the left hand side of an
|
||||
/// assignment. For example, all literal expressions are invalid assignment
|
||||
/// targets.
|
||||
pub(crate) fn assignment_targets(targets: &[Expr]) -> Result<(), LexicalError> {
|
||||
for t in targets {
|
||||
assignment_target(t)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an error if the given target is invalid for the left hand side of
|
||||
/// an assignment.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This returns an error when the given expression is itself or contains an
|
||||
/// expression that is invalid on the left hand side of an assignment. For
|
||||
/// example, all literal expressions are invalid assignment targets.
|
||||
pub(crate) fn assignment_target(target: &Expr) -> Result<(), LexicalError> {
|
||||
// Allowing a glob import here because of its limited scope.
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use self::Expr::*;
|
||||
|
||||
let err = |location: TextSize| -> LexicalError {
|
||||
let error = LexicalErrorType::AssignmentError;
|
||||
LexicalError { error, location }
|
||||
};
|
||||
match *target {
|
||||
BoolOp(ref e) => Err(err(e.range.start())),
|
||||
NamedExpr(ref e) => Err(err(e.range.start())),
|
||||
BinOp(ref e) => Err(err(e.range.start())),
|
||||
UnaryOp(ref e) => Err(err(e.range.start())),
|
||||
Lambda(ref e) => Err(err(e.range.start())),
|
||||
IfExp(ref e) => Err(err(e.range.start())),
|
||||
Dict(ref e) => Err(err(e.range.start())),
|
||||
Set(ref e) => Err(err(e.range.start())),
|
||||
ListComp(ref e) => Err(err(e.range.start())),
|
||||
SetComp(ref e) => Err(err(e.range.start())),
|
||||
DictComp(ref e) => Err(err(e.range.start())),
|
||||
GeneratorExp(ref e) => Err(err(e.range.start())),
|
||||
Await(ref e) => Err(err(e.range.start())),
|
||||
Yield(ref e) => Err(err(e.range.start())),
|
||||
YieldFrom(ref e) => Err(err(e.range.start())),
|
||||
Compare(ref e) => Err(err(e.range.start())),
|
||||
Call(ref e) => Err(err(e.range.start())),
|
||||
FormattedValue(ref e) => Err(err(e.range.start())),
|
||||
// FString is recursive, but all its forms are invalid as an
|
||||
// assignment target, so we can reject it without exploring it.
|
||||
FString(ref e) => Err(err(e.range.start())),
|
||||
StringLiteral(ref e) => Err(err(e.range.start())),
|
||||
BytesLiteral(ref e) => Err(err(e.range.start())),
|
||||
NumberLiteral(ref e) => Err(err(e.range.start())),
|
||||
BooleanLiteral(ref e) => Err(err(e.range.start())),
|
||||
NoneLiteral(ref e) => Err(err(e.range.start())),
|
||||
EllipsisLiteral(ref e) => Err(err(e.range.start())),
|
||||
// This isn't in the Python grammar but is Jupyter notebook specific.
|
||||
// It seems like this should be an error. It does also seem like the
|
||||
// parser prevents this from ever appearing as an assignment target
|
||||
// anyway. ---AG
|
||||
IpyEscapeCommand(ref e) => Err(err(e.range.start())),
|
||||
// The only nested expressions allowed as an assignment target
|
||||
// are star exprs, lists and tuples.
|
||||
Starred(ref e) => assignment_target(&e.value),
|
||||
List(ref e) => assignment_targets(&e.elts),
|
||||
Tuple(ref e) => assignment_targets(&e.elts),
|
||||
// Subscript is recursive and can be invalid, but aren't syntax errors.
|
||||
// For example, `5[1] = 42` is a type error.
|
||||
Subscript(_) => Ok(()),
|
||||
// Similar to Subscript, e.g., `5[1:2] = [42]` is a type error.
|
||||
Slice(_) => Ok(()),
|
||||
// Similar to Subscript, e.g., `"foo".y = 42` is an attribute error.
|
||||
Attribute(_) => Ok(()),
|
||||
// These are always valid as assignment targets.
|
||||
Name(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parse_suite;
|
||||
|
||||
// First we test, broadly, that various kinds of assignments are now
|
||||
// rejected by the parser. e.g., `5 = 3`, `5 += 3`, `(5): int = 3`.
|
||||
|
||||
// Regression test: https://github.com/astral-sh/ruff/issues/6895
|
||||
#[test]
|
||||
fn err_literal_assignment() {
|
||||
let ast = parse_suite(r"5 = 3", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
// This test previously passed before the assignment operator checking
|
||||
// above, but we include it here for good measure.
|
||||
#[test]
|
||||
fn err_assignment_expr() {
|
||||
let ast = parse_suite(r"(5 := 3)", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: UnrecognizedToken(
|
||||
ColonEqual,
|
||||
None,
|
||||
),
|
||||
offset: 3,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_literal_augment_assignment() {
|
||||
let ast = parse_suite(r"5 += 3", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_literal_annotation_assignment() {
|
||||
let ast = parse_suite(r"(5): int = 3", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 1,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
// Now we exhaustively test all possible cases where assignment can fail.
|
||||
|
||||
#[test]
|
||||
fn err_bool_op() {
|
||||
let ast = parse_suite(r"x or y = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_named_expr() {
|
||||
let ast = parse_suite(r"(x := 5) = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 1,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_bin_op() {
|
||||
let ast = parse_suite(r"x + y = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_unary_op() {
|
||||
let ast = parse_suite(r"-x = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_lambda() {
|
||||
let ast = parse_suite(r"(lambda _: 1) = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 1,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_if_exp() {
|
||||
let ast = parse_suite(r"a if b else c = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_dict() {
|
||||
let ast = parse_suite(r"{'a':5} = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_set() {
|
||||
let ast = parse_suite(r"{a} = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_list_comp() {
|
||||
let ast = parse_suite(r"[x for x in xs] = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_set_comp() {
|
||||
let ast = parse_suite(r"{x for x in xs} = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_dict_comp() {
|
||||
let ast = parse_suite(r"{x: x*2 for x in xs} = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_generator_exp() {
|
||||
let ast = parse_suite(r"(x for x in xs) = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_await() {
|
||||
let ast = parse_suite(r"await x = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_yield() {
|
||||
let ast = parse_suite(r"(yield x) = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 1,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_yield_from() {
|
||||
let ast = parse_suite(r"(yield from xs) = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 1,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_compare() {
|
||||
let ast = parse_suite(r"a < b < c = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_call() {
|
||||
let ast = parse_suite(r"foo() = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_formatted_value() {
|
||||
// N.B. It looks like the parser can't generate a top-level
|
||||
// FormattedValue, where as the official Python AST permits
|
||||
// representing a single f-string containing just a variable as a
|
||||
// FormattedValue directly.
|
||||
//
|
||||
// Bottom line is that because of this, this test is (at present)
|
||||
// duplicative with the `fstring` test. That is, in theory these tests
|
||||
// could fail independently, but in practice their failure or success
|
||||
// is coupled.
|
||||
//
|
||||
// See: https://docs.python.org/3/library/ast.html#ast.FormattedValue
|
||||
let ast = parse_suite(r#"f"{quux}" = 42"#, "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_fstring() {
|
||||
let ast = parse_suite(r#"f"{foo} and {bar}" = 42"#, "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_string_literal() {
|
||||
let ast = parse_suite(r#""foo" = 42"#, "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_bytes_literal() {
|
||||
let ast = parse_suite(r#"b"foo" = 42"#, "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_number_literal() {
|
||||
let ast = parse_suite(r"123 = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_boolean_literal() {
|
||||
let ast = parse_suite(r"True = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_none_literal() {
|
||||
let ast = parse_suite(r"None = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_ellipsis_literal() {
|
||||
let ast = parse_suite(r"... = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 0,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_starred() {
|
||||
let ast = parse_suite(r"*foo() = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 1,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_list() {
|
||||
let ast = parse_suite(r"[x, foo(), y] = [42, 42, 42]", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 4,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_list_nested() {
|
||||
let ast = parse_suite(r"[[a, b], [[42]], d] = [[1, 2], [[3]], 4]", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 11,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_tuple() {
|
||||
let ast = parse_suite(r"(x, foo(), y) = (42, 42, 42)", "<test>");
|
||||
insta::assert_debug_snapshot!(ast, @r#"
|
||||
Err(
|
||||
ParseError {
|
||||
error: Lexical(
|
||||
AssignmentError,
|
||||
),
|
||||
offset: 4,
|
||||
source_path: "<test>",
|
||||
},
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
// This last group of tests checks that assignments we expect to be parsed
|
||||
// (including some interesting ones) continue to be parsed successfully.
|
||||
|
||||
#[test]
|
||||
fn ok_starred() {
|
||||
let ast = parse_suite(r"*foo = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_list() {
|
||||
let ast = parse_suite(r"[x, y, z] = [1, 2, 3]", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_tuple() {
|
||||
let ast = parse_suite(r"(x, y, z) = (1, 2, 3)", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_subscript_normal() {
|
||||
let ast = parse_suite(r"x[0] = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
// This is actually a type error, not a syntax error. So check that it
|
||||
// doesn't fail parsing.
|
||||
#[test]
|
||||
fn ok_subscript_weird() {
|
||||
let ast = parse_suite(r"5[0] = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_slice_normal() {
|
||||
let ast = parse_suite(r"x[1:2] = [42]", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
// This is actually a type error, not a syntax error. So check that it
|
||||
// doesn't fail parsing.
|
||||
#[test]
|
||||
fn ok_slice_weird() {
|
||||
let ast = parse_suite(r"5[1:2] = [42]", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_attribute_normal() {
|
||||
let ast = parse_suite(r"foo.bar = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
// This is actually an attribute error, not a syntax error. So check that
|
||||
// it doesn't fail parsing.
|
||||
#[test]
|
||||
fn ok_attribute_weird() {
|
||||
let ast = parse_suite(r#""foo".y = 42"#, "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_name() {
|
||||
let ast = parse_suite(r"foo = 42", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
// This is a sanity test for what looks like an ipython directive being
|
||||
// assigned to. Although this doesn't actually parse as an assignment
|
||||
// statement, but rather, a directive whose value is `foo = 42`.
|
||||
#[test]
|
||||
fn ok_ipy_escape_command() {
|
||||
use crate::Mode;
|
||||
|
||||
let src = r"!foo = 42";
|
||||
let tokens = crate::lexer::lex(src, Mode::Ipython);
|
||||
let ast = crate::parse_tokens(tokens, src, Mode::Ipython, "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_assignment_expr() {
|
||||
let ast = parse_suite(r"(x := 5)", "<test>");
|
||||
insta::assert_debug_snapshot!(ast);
|
||||
}
|
||||
}
|
||||
@@ -1344,6 +1344,8 @@ pub enum LexicalErrorType {
|
||||
LineContinuationError,
|
||||
/// An unexpected end of file was encountered.
|
||||
Eof,
|
||||
/// Occurs when a syntactically invalid assignment was encountered.
|
||||
AssignmentError,
|
||||
/// An unexpected error occurred.
|
||||
OtherError(String),
|
||||
}
|
||||
@@ -1389,6 +1391,7 @@ impl std::fmt::Display for LexicalErrorType {
|
||||
write!(f, "unexpected character after line continuation character")
|
||||
}
|
||||
LexicalErrorType::Eof => write!(f, "unexpected EOF while parsing"),
|
||||
LexicalErrorType::AssignmentError => write!(f, "invalid assignment target"),
|
||||
LexicalErrorType::OtherError(msg) => write!(f, "{msg}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ use crate::lexer::LexResult;
|
||||
mod function;
|
||||
// Skip flattening lexer to distinguish from full ruff_python_parser
|
||||
mod context;
|
||||
mod invalid;
|
||||
pub mod lexer;
|
||||
mod parser;
|
||||
mod soft_keywords;
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::{
|
||||
context::set_context,
|
||||
string::{StringType, concatenate_strings, parse_fstring_middle, parse_string_literal},
|
||||
token::{self, StringKind},
|
||||
invalid,
|
||||
};
|
||||
use lalrpop_util::ParseError;
|
||||
|
||||
@@ -108,12 +109,12 @@ DelStatement: ast::Stmt = {
|
||||
};
|
||||
|
||||
ExpressionStatement: ast::Stmt = {
|
||||
<location:@L> <expression:TestOrStarExprList> <suffix:AssignSuffix*> <end_location:@R> => {
|
||||
<location:@L> <expression:TestOrStarExprList> <suffix:AssignSuffix*> <end_location:@R> =>? {
|
||||
// Just an expression, no assignment:
|
||||
if suffix.is_empty() {
|
||||
ast::Stmt::Expr(
|
||||
Ok(ast::Stmt::Expr(
|
||||
ast::StmtExpr { value: Box::new(expression.into()), range: (location..end_location).into() }
|
||||
)
|
||||
))
|
||||
} else {
|
||||
let mut targets = vec![set_context(expression.into(), ast::ExprContext::Store)];
|
||||
let mut values = suffix;
|
||||
@@ -123,25 +124,27 @@ ExpressionStatement: ast::Stmt = {
|
||||
for target in values {
|
||||
targets.push(set_context(target.into(), ast::ExprContext::Store));
|
||||
}
|
||||
|
||||
ast::Stmt::Assign(
|
||||
invalid::assignment_targets(&targets)?;
|
||||
Ok(ast::Stmt::Assign(
|
||||
ast::StmtAssign { targets, value, range: (location..end_location).into() }
|
||||
)
|
||||
))
|
||||
}
|
||||
},
|
||||
<location:@L> <target:TestOrStarExprList> <op:AugAssign> <rhs:TestListOrYieldExpr> <end_location:@R> => {
|
||||
ast::Stmt::AugAssign(
|
||||
<location:@L> <target:TestOrStarExprList> <op:AugAssign> <rhs:TestListOrYieldExpr> <end_location:@R> =>? {
|
||||
invalid::assignment_target(&target.expr)?;
|
||||
Ok(ast::Stmt::AugAssign(
|
||||
ast::StmtAugAssign {
|
||||
target: Box::new(set_context(target.into(), ast::ExprContext::Store)),
|
||||
op,
|
||||
value: Box::new(rhs.into()),
|
||||
range: (location..end_location).into()
|
||||
},
|
||||
)
|
||||
))
|
||||
},
|
||||
<location:@L> <target:Test<"all">> ":" <annotation:Test<"all">> <rhs:AssignSuffix?> <end_location:@R> => {
|
||||
<location:@L> <target:Test<"all">> ":" <annotation:Test<"all">> <rhs:AssignSuffix?> <end_location:@R> =>? {
|
||||
let simple = target.expr.is_name_expr();
|
||||
ast::Stmt::AnnAssign(
|
||||
invalid::assignment_target(&target.expr)?;
|
||||
Ok(ast::Stmt::AnnAssign(
|
||||
ast::StmtAnnAssign {
|
||||
target: Box::new(set_context(target.into(), ast::ExprContext::Store)),
|
||||
annotation: Box::new(annotation.into()),
|
||||
@@ -149,7 +152,7 @@ ExpressionStatement: ast::Stmt = {
|
||||
simple,
|
||||
range: (location..end_location).into()
|
||||
},
|
||||
)
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// auto-generated: "lalrpop 0.20.0"
|
||||
// sha3: c798bc6e7bd9950e88dd5d950470865a75b5ff0352f4fc7fb51f13147de6ba6c
|
||||
// sha3: b8ac4a859b69d580e50733d39c96a3fe018f568e71e532ebb3153a19902e64e5
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use ruff_python_ast::{self as ast, Int, IpyEscapeKind};
|
||||
use crate::{
|
||||
@@ -10,6 +10,7 @@ use crate::{
|
||||
context::set_context,
|
||||
string::{StringType, concatenate_strings, parse_fstring_middle, parse_string_literal},
|
||||
token::{self, StringKind},
|
||||
invalid,
|
||||
};
|
||||
use lalrpop_util::ParseError;
|
||||
#[allow(unused_extern_crates)]
|
||||
@@ -33,6 +34,7 @@ mod __parse__Top {
|
||||
context::set_context,
|
||||
string::{StringType, concatenate_strings, parse_fstring_middle, parse_string_literal},
|
||||
token::{self, StringKind},
|
||||
invalid,
|
||||
};
|
||||
use lalrpop_util::ParseError;
|
||||
#[allow(unused_extern_crates)]
|
||||
@@ -13725,19 +13727,76 @@ mod __parse__Top {
|
||||
__reduce356(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>)
|
||||
}
|
||||
357 => {
|
||||
__reduce357(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>)
|
||||
// ExpressionStatement = GenericList<TestOrStarExpr> => ActionFn(1752);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym0.2;
|
||||
let __nt = match super::__action1752::<>(source_code, mode, __sym0) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(1, 137)
|
||||
}
|
||||
358 => {
|
||||
__reduce358(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>)
|
||||
// ExpressionStatement = GenericList<TestOrStarExpr>, AssignSuffix+ => ActionFn(1753);
|
||||
assert!(__symbols.len() >= 2);
|
||||
let __sym1 = __pop_Variant17(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym1.2;
|
||||
let __nt = match super::__action1753::<>(source_code, mode, __sym0, __sym1) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(2, 137)
|
||||
}
|
||||
359 => {
|
||||
__reduce359(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>)
|
||||
// ExpressionStatement = GenericList<TestOrStarExpr>, AugAssign, TestListOrYieldExpr => ActionFn(1754);
|
||||
assert!(__symbols.len() >= 3);
|
||||
let __sym2 = __pop_Variant15(__symbols);
|
||||
let __sym1 = __pop_Variant49(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym2.2;
|
||||
let __nt = match super::__action1754::<>(source_code, mode, __sym0, __sym1, __sym2) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(3, 137)
|
||||
}
|
||||
360 => {
|
||||
__reduce360(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>)
|
||||
// ExpressionStatement = Test<"all">, ":", Test<"all">, AssignSuffix => ActionFn(1531);
|
||||
assert!(__symbols.len() >= 4);
|
||||
let __sym3 = __pop_Variant15(__symbols);
|
||||
let __sym2 = __pop_Variant15(__symbols);
|
||||
let __sym1 = __pop_Variant0(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym3.2;
|
||||
let __nt = match super::__action1531::<>(source_code, mode, __sym0, __sym1, __sym2, __sym3) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(4, 137)
|
||||
}
|
||||
361 => {
|
||||
__reduce361(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>)
|
||||
// ExpressionStatement = Test<"all">, ":", Test<"all"> => ActionFn(1532);
|
||||
assert!(__symbols.len() >= 3);
|
||||
let __sym2 = __pop_Variant15(__symbols);
|
||||
let __sym1 = __pop_Variant0(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym2.2;
|
||||
let __nt = match super::__action1532::<>(source_code, mode, __sym0, __sym1, __sym2) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(3, 137)
|
||||
}
|
||||
362 => {
|
||||
// FStringConversion = "!", name => ActionFn(800);
|
||||
@@ -24718,103 +24777,6 @@ mod __parse__Top {
|
||||
__symbols.push((__start, __Symbol::Variant15(__nt), __end));
|
||||
(1, 136)
|
||||
}
|
||||
pub(crate) fn __reduce357<
|
||||
>(
|
||||
source_code: &str,
|
||||
mode: Mode,
|
||||
__lookahead_start: Option<&TextSize>,
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>,
|
||||
_: core::marker::PhantomData<()>,
|
||||
) -> (usize, usize)
|
||||
{
|
||||
// ExpressionStatement = GenericList<TestOrStarExpr> => ActionFn(1752);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym0.2;
|
||||
let __nt = super::__action1752::<>(source_code, mode, __sym0);
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(1, 137)
|
||||
}
|
||||
pub(crate) fn __reduce358<
|
||||
>(
|
||||
source_code: &str,
|
||||
mode: Mode,
|
||||
__lookahead_start: Option<&TextSize>,
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>,
|
||||
_: core::marker::PhantomData<()>,
|
||||
) -> (usize, usize)
|
||||
{
|
||||
// ExpressionStatement = GenericList<TestOrStarExpr>, AssignSuffix+ => ActionFn(1753);
|
||||
assert!(__symbols.len() >= 2);
|
||||
let __sym1 = __pop_Variant17(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym1.2;
|
||||
let __nt = super::__action1753::<>(source_code, mode, __sym0, __sym1);
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(2, 137)
|
||||
}
|
||||
pub(crate) fn __reduce359<
|
||||
>(
|
||||
source_code: &str,
|
||||
mode: Mode,
|
||||
__lookahead_start: Option<&TextSize>,
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>,
|
||||
_: core::marker::PhantomData<()>,
|
||||
) -> (usize, usize)
|
||||
{
|
||||
// ExpressionStatement = GenericList<TestOrStarExpr>, AugAssign, TestListOrYieldExpr => ActionFn(1754);
|
||||
assert!(__symbols.len() >= 3);
|
||||
let __sym2 = __pop_Variant15(__symbols);
|
||||
let __sym1 = __pop_Variant49(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym2.2;
|
||||
let __nt = super::__action1754::<>(source_code, mode, __sym0, __sym1, __sym2);
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(3, 137)
|
||||
}
|
||||
pub(crate) fn __reduce360<
|
||||
>(
|
||||
source_code: &str,
|
||||
mode: Mode,
|
||||
__lookahead_start: Option<&TextSize>,
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>,
|
||||
_: core::marker::PhantomData<()>,
|
||||
) -> (usize, usize)
|
||||
{
|
||||
// ExpressionStatement = Test<"all">, ":", Test<"all">, AssignSuffix => ActionFn(1531);
|
||||
assert!(__symbols.len() >= 4);
|
||||
let __sym3 = __pop_Variant15(__symbols);
|
||||
let __sym2 = __pop_Variant15(__symbols);
|
||||
let __sym1 = __pop_Variant0(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym3.2;
|
||||
let __nt = super::__action1531::<>(source_code, mode, __sym0, __sym1, __sym2, __sym3);
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(4, 137)
|
||||
}
|
||||
pub(crate) fn __reduce361<
|
||||
>(
|
||||
source_code: &str,
|
||||
mode: Mode,
|
||||
__lookahead_start: Option<&TextSize>,
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>,
|
||||
_: core::marker::PhantomData<()>,
|
||||
) -> (usize, usize)
|
||||
{
|
||||
// ExpressionStatement = Test<"all">, ":", Test<"all"> => ActionFn(1532);
|
||||
assert!(__symbols.len() >= 3);
|
||||
let __sym2 = __pop_Variant15(__symbols);
|
||||
let __sym1 = __pop_Variant0(__symbols);
|
||||
let __sym0 = __pop_Variant15(__symbols);
|
||||
let __start = __sym0.0;
|
||||
let __end = __sym2.2;
|
||||
let __nt = super::__action1532::<>(source_code, mode, __sym0, __sym1, __sym2);
|
||||
__symbols.push((__start, __Symbol::Variant37(__nt), __end));
|
||||
(3, 137)
|
||||
}
|
||||
pub(crate) fn __reduce363<
|
||||
>(
|
||||
source_code: &str,
|
||||
@@ -32789,14 +32751,14 @@ fn __action26<
|
||||
(_, expression, _): (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
(_, suffix, _): (TextSize, alloc::vec::Vec<ast::ParenthesizedExpr>, TextSize),
|
||||
(_, end_location, _): (TextSize, TextSize, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
{
|
||||
// Just an expression, no assignment:
|
||||
if suffix.is_empty() {
|
||||
ast::Stmt::Expr(
|
||||
Ok(ast::Stmt::Expr(
|
||||
ast::StmtExpr { value: Box::new(expression.into()), range: (location..end_location).into() }
|
||||
)
|
||||
))
|
||||
} else {
|
||||
let mut targets = vec![set_context(expression.into(), ast::ExprContext::Store)];
|
||||
let mut values = suffix;
|
||||
@@ -32806,10 +32768,10 @@ fn __action26<
|
||||
for target in values {
|
||||
targets.push(set_context(target.into(), ast::ExprContext::Store));
|
||||
}
|
||||
|
||||
ast::Stmt::Assign(
|
||||
invalid::assignment_targets(&targets)?;
|
||||
Ok(ast::Stmt::Assign(
|
||||
ast::StmtAssign { targets, value, range: (location..end_location).into() }
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32825,17 +32787,18 @@ fn __action27<
|
||||
(_, op, _): (TextSize, ast::Operator, TextSize),
|
||||
(_, rhs, _): (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
(_, end_location, _): (TextSize, TextSize, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
{
|
||||
ast::Stmt::AugAssign(
|
||||
invalid::assignment_target(&target.expr)?;
|
||||
Ok(ast::Stmt::AugAssign(
|
||||
ast::StmtAugAssign {
|
||||
target: Box::new(set_context(target.into(), ast::ExprContext::Store)),
|
||||
op,
|
||||
value: Box::new(rhs.into()),
|
||||
range: (location..end_location).into()
|
||||
},
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32851,11 +32814,12 @@ fn __action28<
|
||||
(_, annotation, _): (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
(_, rhs, _): (TextSize, core::option::Option<ast::ParenthesizedExpr>, TextSize),
|
||||
(_, end_location, _): (TextSize, TextSize, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
{
|
||||
let simple = target.expr.is_name_expr();
|
||||
ast::Stmt::AnnAssign(
|
||||
invalid::assignment_target(&target.expr)?;
|
||||
Ok(ast::Stmt::AnnAssign(
|
||||
ast::StmtAnnAssign {
|
||||
target: Box::new(set_context(target.into(), ast::ExprContext::Store)),
|
||||
annotation: Box::new(annotation.into()),
|
||||
@@ -32863,7 +32827,7 @@ fn __action28<
|
||||
simple,
|
||||
range: (location..end_location).into()
|
||||
},
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48215,7 +48179,7 @@ fn __action797<
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__1: (TextSize, alloc::vec::Vec<ast::ParenthesizedExpr>, TextSize),
|
||||
__2: (TextSize, TextSize, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.0;
|
||||
@@ -48246,7 +48210,7 @@ fn __action798<
|
||||
__1: (TextSize, ast::Operator, TextSize),
|
||||
__2: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__3: (TextSize, TextSize, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.0;
|
||||
@@ -48279,7 +48243,7 @@ fn __action799<
|
||||
__2: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__3: (TextSize, core::option::Option<ast::ParenthesizedExpr>, TextSize),
|
||||
__4: (TextSize, TextSize, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.0;
|
||||
@@ -64278,7 +64242,7 @@ fn __action1309<
|
||||
mode: Mode,
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__1: (TextSize, alloc::vec::Vec<ast::ParenthesizedExpr>, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __1.2;
|
||||
let __end0 = __1.2;
|
||||
@@ -64307,7 +64271,7 @@ fn __action1310<
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__1: (TextSize, ast::Operator, TextSize),
|
||||
__2: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __2.2;
|
||||
let __end0 = __2.2;
|
||||
@@ -64338,7 +64302,7 @@ fn __action1311<
|
||||
__1: (TextSize, token::Tok, TextSize),
|
||||
__2: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__3: (TextSize, core::option::Option<ast::ParenthesizedExpr>, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __3.2;
|
||||
let __end0 = __3.2;
|
||||
@@ -71035,7 +70999,7 @@ fn __action1529<
|
||||
source_code: &str,
|
||||
mode: Mode,
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __0.2;
|
||||
let __end0 = __0.2;
|
||||
@@ -71062,7 +71026,7 @@ fn __action1530<
|
||||
mode: Mode,
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__1: (TextSize, alloc::vec::Vec<ast::ParenthesizedExpr>, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __1.0;
|
||||
let __end0 = __1.2;
|
||||
@@ -71090,7 +71054,7 @@ fn __action1531<
|
||||
__1: (TextSize, token::Tok, TextSize),
|
||||
__2: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__3: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __3.0;
|
||||
let __end0 = __3.2;
|
||||
@@ -71119,7 +71083,7 @@ fn __action1532<
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__1: (TextSize, token::Tok, TextSize),
|
||||
__2: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __2.2;
|
||||
let __end0 = __2.2;
|
||||
@@ -78391,7 +78355,7 @@ fn __action1752<
|
||||
source_code: &str,
|
||||
mode: Mode,
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.2;
|
||||
@@ -78416,7 +78380,7 @@ fn __action1753<
|
||||
mode: Mode,
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__1: (TextSize, alloc::vec::Vec<ast::ParenthesizedExpr>, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.2;
|
||||
@@ -78443,7 +78407,7 @@ fn __action1754<
|
||||
__0: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
__1: (TextSize, ast::Operator, TextSize),
|
||||
__2: (TextSize, ast::ParenthesizedExpr, TextSize),
|
||||
) -> ast::Stmt
|
||||
) -> Result<ast::Stmt,__lalrpop_util::ParseError<TextSize,token::Tok,LexicalError>>
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.2;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 0..8,
|
||||
value: NamedExpr(
|
||||
ExprNamedExpr {
|
||||
range: 1..7,
|
||||
target: Name(
|
||||
ExprName {
|
||||
range: 1..2,
|
||||
id: "x",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 6..7,
|
||||
value: Int(
|
||||
5,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..12,
|
||||
targets: [
|
||||
Attribute(
|
||||
ExprAttribute {
|
||||
range: 0..7,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 0..3,
|
||||
id: "foo",
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
attr: Identifier {
|
||||
id: "bar",
|
||||
range: 4..7,
|
||||
},
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 10..12,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..12,
|
||||
targets: [
|
||||
Attribute(
|
||||
ExprAttribute {
|
||||
range: 0..7,
|
||||
value: StringLiteral(
|
||||
ExprStringLiteral {
|
||||
range: 0..5,
|
||||
value: "foo",
|
||||
unicode: false,
|
||||
implicit_concatenated: false,
|
||||
},
|
||||
),
|
||||
attr: Identifier {
|
||||
id: "y",
|
||||
range: 6..7,
|
||||
},
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 10..12,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..9,
|
||||
body: [
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 0..9,
|
||||
kind: Shell,
|
||||
value: "foo = 42",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..21,
|
||||
targets: [
|
||||
List(
|
||||
ExprList {
|
||||
range: 0..9,
|
||||
elts: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 1..2,
|
||||
id: "x",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 4..5,
|
||||
id: "y",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 7..8,
|
||||
id: "z",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: List(
|
||||
ExprList {
|
||||
range: 12..21,
|
||||
elts: [
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 13..14,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 16..17,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
},
|
||||
),
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 19..20,
|
||||
value: Int(
|
||||
3,
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..8,
|
||||
targets: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 0..3,
|
||||
id: "foo",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 6..8,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..13,
|
||||
targets: [
|
||||
Subscript(
|
||||
ExprSubscript {
|
||||
range: 0..6,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 0..1,
|
||||
id: "x",
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
slice: Slice(
|
||||
ExprSlice {
|
||||
range: 2..5,
|
||||
lower: Some(
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 2..3,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
upper: Some(
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 4..5,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
step: None,
|
||||
},
|
||||
),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: List(
|
||||
ExprList {
|
||||
range: 9..13,
|
||||
elts: [
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 10..12,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..13,
|
||||
targets: [
|
||||
Subscript(
|
||||
ExprSubscript {
|
||||
range: 0..6,
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 0..1,
|
||||
value: Int(
|
||||
5,
|
||||
),
|
||||
},
|
||||
),
|
||||
slice: Slice(
|
||||
ExprSlice {
|
||||
range: 2..5,
|
||||
lower: Some(
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 2..3,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
upper: Some(
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 4..5,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
step: None,
|
||||
},
|
||||
),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: List(
|
||||
ExprList {
|
||||
range: 9..13,
|
||||
elts: [
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 10..12,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..9,
|
||||
targets: [
|
||||
Starred(
|
||||
ExprStarred {
|
||||
range: 0..4,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 1..4,
|
||||
id: "foo",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 7..9,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..9,
|
||||
targets: [
|
||||
Subscript(
|
||||
ExprSubscript {
|
||||
range: 0..4,
|
||||
value: Name(
|
||||
ExprName {
|
||||
range: 0..1,
|
||||
id: "x",
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
slice: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 2..3,
|
||||
value: Int(
|
||||
0,
|
||||
),
|
||||
},
|
||||
),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 7..9,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..9,
|
||||
targets: [
|
||||
Subscript(
|
||||
ExprSubscript {
|
||||
range: 0..4,
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 0..1,
|
||||
value: Int(
|
||||
5,
|
||||
),
|
||||
},
|
||||
),
|
||||
slice: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 2..3,
|
||||
value: Int(
|
||||
0,
|
||||
),
|
||||
},
|
||||
),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 7..9,
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/src/invalid.rs
|
||||
expression: ast
|
||||
---
|
||||
Ok(
|
||||
[
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 0..21,
|
||||
targets: [
|
||||
Tuple(
|
||||
ExprTuple {
|
||||
range: 0..9,
|
||||
elts: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 1..2,
|
||||
id: "x",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 4..5,
|
||||
id: "y",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
range: 7..8,
|
||||
id: "z",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: Tuple(
|
||||
ExprTuple {
|
||||
range: 12..21,
|
||||
elts: [
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 13..14,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 16..17,
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
},
|
||||
),
|
||||
NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 19..20,
|
||||
value: Int(
|
||||
3,
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -616,6 +616,16 @@ impl<'a> SemanticModel<'a> {
|
||||
self.resolved_names.get(&name.into()).copied()
|
||||
}
|
||||
|
||||
/// Resolves the [`ast::ExprName`] to the [`BindingId`] of the symbol it refers to, if it's the
|
||||
/// only binding to that name in its scope.
|
||||
pub fn only_binding(&self, name: &ast::ExprName) -> Option<BindingId> {
|
||||
self.resolve_name(name).filter(|id| {
|
||||
let binding = self.binding(*id);
|
||||
let scope = &self.scopes[binding.scope];
|
||||
scope.shadowed_binding(*id).is_none()
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves the [`Expr`] to a fully-qualified symbol-name, if `value` resolves to an imported
|
||||
/// or builtin symbol.
|
||||
///
|
||||
|
||||
@@ -36,7 +36,7 @@ console_log = { version = "1.0.0" }
|
||||
log = { workspace = true }
|
||||
|
||||
serde = { workspace = true }
|
||||
serde-wasm-bindgen = { version = "0.6.0" }
|
||||
serde-wasm-bindgen = { version = "0.6.1" }
|
||||
wasm-bindgen = { version = "0.2.84" }
|
||||
js-sys = { version = "0.3.61" }
|
||||
|
||||
|
||||
@@ -298,10 +298,18 @@ pub struct Options {
|
||||
/// For example, to represent supporting Python >=3.10 or ==3.10
|
||||
/// specify `target-version = "py310"`.
|
||||
///
|
||||
/// If omitted, and Ruff is configured via a `pyproject.toml` file, the
|
||||
/// target version will be inferred from its `project.requires-python`
|
||||
/// field (e.g., `requires-python = ">=3.8"`). If Ruff is configured via
|
||||
/// `ruff.toml` or `.ruff.toml`, no such inference will be performed.
|
||||
/// If you're already using a `pyproject.toml` file, we recommend
|
||||
/// `project.requires-python` instead, as it's based on Python packaging
|
||||
/// standards, and will be respected by other tools. For example, Ruff
|
||||
/// treats the following as identical to `target-version = "py38"`:
|
||||
///
|
||||
/// ```toml
|
||||
/// [project]
|
||||
/// requires-python = ">=3.8"
|
||||
/// ```
|
||||
///
|
||||
/// If both are specified, `target-version` takes precedence over
|
||||
/// `requires-python`.
|
||||
#[option(
|
||||
default = r#""py38""#,
|
||||
value_type = r#""py37" | "py38" | "py39" | "py310" | "py311" | "py312""#,
|
||||
|
||||
@@ -7,147 +7,220 @@ semantics are the same.
|
||||
|
||||
For a complete enumeration of the available configuration options, see [_Settings_](settings.md).
|
||||
|
||||
## Using `pyproject.toml`
|
||||
|
||||
If left unspecified, Ruff's default configuration is equivalent to:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"venv",
|
||||
]
|
||||
=== "pyproject.toml"
|
||||
|
||||
# Same as Black.
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"venv",
|
||||
]
|
||||
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
# Same as Black.
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||
# McCabe complexity (`C901`) by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
[tool.ruff.lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||
# McCabe complexity (`C901`) by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
[tool.ruff.format]
|
||||
# Like Black, use double quotes for strings.
|
||||
quote-style = "double"
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
# Like Black, indent with spaces, rather than tabs.
|
||||
indent-style = "space"
|
||||
[tool.ruff.format]
|
||||
# Like Black, use double quotes for strings.
|
||||
quote-style = "double"
|
||||
|
||||
# Like Black, respect magic trailing commas.
|
||||
skip-magic-trailing-comma = false
|
||||
# Like Black, indent with spaces, rather than tabs.
|
||||
indent-style = "space"
|
||||
|
||||
# Like Black, automatically detect the appropriate line ending.
|
||||
line-ending = "auto"
|
||||
```
|
||||
# Like Black, respect magic trailing commas.
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
# Like Black, automatically detect the appropriate line ending.
|
||||
line-ending = "auto"
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"venv",
|
||||
]
|
||||
|
||||
# Same as Black.
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
|
||||
[lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||
# McCabe complexity (`C901`) by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[format]
|
||||
# Like Black, use double quotes for strings.
|
||||
quote-style = "double"
|
||||
|
||||
# Like Black, indent with spaces, rather than tabs.
|
||||
indent-style = "space"
|
||||
|
||||
# Like Black, respect magic trailing commas.
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
# Like Black, automatically detect the appropriate line ending.
|
||||
line-ending = "auto"
|
||||
```
|
||||
|
||||
As an example, the following would configure Ruff to:
|
||||
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
|
||||
select = ["E4", "E7", "E9", "F", "B"]
|
||||
=== "pyproject.toml"
|
||||
|
||||
# 2. Avoid enforcing line-length violations (`E501`)
|
||||
ignore = ["E501"]
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
|
||||
select = ["E4", "E7", "E9", "F", "B"]
|
||||
|
||||
# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
|
||||
unfixable = ["B"]
|
||||
# 2. Avoid enforcing line-length violations (`E501`)
|
||||
ignore = ["E501"]
|
||||
|
||||
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories.
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"**/{tests,docs,tools}/*" = ["E402"]
|
||||
# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
|
||||
unfixable = ["B"]
|
||||
|
||||
[tool.ruff.format]
|
||||
# 5. Use single quotes for non-triple-quoted strings.
|
||||
quote-style = "single"
|
||||
```
|
||||
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories.
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"**/{tests,docs,tools}/*" = ["E402"]
|
||||
|
||||
[tool.ruff.format]
|
||||
# 5. Use single quotes for non-triple-quoted strings.
|
||||
quote-style = "single"
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
[lint]
|
||||
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
|
||||
select = ["E4", "E7", "E9", "F", "B"]
|
||||
|
||||
# 2. Avoid enforcing line-length violations (`E501`)
|
||||
ignore = ["E501"]
|
||||
|
||||
# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
|
||||
unfixable = ["B"]
|
||||
|
||||
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories.
|
||||
[lint.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"**/{tests,docs,tools}/*" = ["E402"]
|
||||
|
||||
[format]
|
||||
# 5. Use single quotes for non-triple-quoted strings.
|
||||
quote-style = "single"
|
||||
```
|
||||
|
||||
Linter plugin configurations are expressed as subsections, e.g.:
|
||||
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
# Add "Q" to the list of enabled codes.
|
||||
select = ["E4", "E7", "E9", "F", "Q"]
|
||||
=== "pyproject.toml"
|
||||
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
# Add "Q" to the list of enabled codes.
|
||||
select = ["E4", "E7", "E9", "F", "Q"]
|
||||
|
||||
For a complete enumeration of the available configuration options, see [_Settings_](settings.md).
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
## Using `ruff.toml`
|
||||
=== "ruff.toml"
|
||||
|
||||
As an alternative to `pyproject.toml`, Ruff will also respect a `ruff.toml` (or `.ruff.toml`) file,
|
||||
which implements an equivalent schema (though in the `ruff.toml` and `.ruff.toml` versions, the
|
||||
```toml
|
||||
[lint]
|
||||
# Add "Q" to the list of enabled codes.
|
||||
select = ["E4", "E7", "E9", "F", "Q"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
```
|
||||
|
||||
Ruff respects `pyproject.toml`, `ruff.toml`, and `.ruff.toml` files. All three implement
|
||||
an equivalent schema (though in the `ruff.toml` and `.ruff.toml` versions, the
|
||||
`[tool.ruff]` header is omitted).
|
||||
|
||||
For example, the `pyproject.toml` described above would be represented via the following
|
||||
`ruff.toml` (or `.ruff.toml`):
|
||||
|
||||
```toml
|
||||
[lint]
|
||||
# Enable flake8-bugbear (`B`) rules.
|
||||
select = ["E4", "E7", "E9", "F", "B"]
|
||||
|
||||
# Never enforce `E501` (line length violations).
|
||||
ignore = ["E501"]
|
||||
|
||||
# Avoid trying to fix flake8-bugbear (`B`) violations.
|
||||
unfixable = ["B"]
|
||||
|
||||
# Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories.
|
||||
[lint.per-file-ignores]
|
||||
"__init__.py" = ["E402"]
|
||||
"**/{tests,docs,tools}/*" = ["E402"]
|
||||
|
||||
[format]
|
||||
# Use single quotes for non-triple-quoted strings.
|
||||
quote-style = "single"
|
||||
```
|
||||
|
||||
For a complete enumeration of the available configuration options, see [_Settings_](settings.md).
|
||||
|
||||
## `pyproject.toml` discovery
|
||||
## Config file discovery
|
||||
|
||||
Similar to [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy),
|
||||
Ruff supports hierarchical configuration, such that the "closest" `pyproject.toml` file in the
|
||||
directory hierarchy is used for every individual file, with all paths in the `pyproject.toml` file
|
||||
Ruff supports hierarchical configuration, such that the "closest" config file in the
|
||||
directory hierarchy is used for every individual file, with all paths in the config file
|
||||
(e.g., `exclude` globs, `src` paths) being resolved relative to the directory containing that
|
||||
`pyproject.toml` file.
|
||||
config file.
|
||||
|
||||
There are a few exceptions to these rules:
|
||||
|
||||
@@ -156,47 +229,69 @@ There are a few exceptions to these rules:
|
||||
1. If a configuration file is passed directly via `--config`, those settings are used for _all_
|
||||
analyzed files, and any relative paths in that configuration file (like `exclude` globs or
|
||||
`src` paths) are resolved relative to the _current_ working directory.
|
||||
1. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using
|
||||
1. If no config file is found in the filesystem hierarchy, Ruff will fall back to using
|
||||
a default configuration. If a user-specific configuration file exists
|
||||
at `${config_dir}/ruff/pyproject.toml`, that file will be used instead of the default
|
||||
configuration, with `${config_dir}` being determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html)
|
||||
crate, and all relative paths being again resolved relative to the _current working directory_.
|
||||
1. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via
|
||||
1. Any config-file-supported settings that are provided on the command-line (e.g., via
|
||||
`--select`) will override the settings in _every_ resolved configuration file.
|
||||
|
||||
Unlike [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy),
|
||||
Ruff does not merge settings across configuration files; instead, the "closest" configuration file
|
||||
is used, and any parent configuration files are ignored. In lieu of this implicit cascade, Ruff
|
||||
supports an [`extend`](settings.md#extend) field, which allows you to inherit the settings from another
|
||||
`pyproject.toml` file, like so:
|
||||
config file, like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Extend the `pyproject.toml` file in the parent directory...
|
||||
extend = "../pyproject.toml"
|
||||
=== "pyproject.toml"
|
||||
|
||||
# ...but use a different line length.
|
||||
line-length = 100
|
||||
```
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# Extend the `pyproject.toml` file in the parent directory...
|
||||
extend = "../pyproject.toml"
|
||||
|
||||
All of the above rules apply equivalently to `ruff.toml` and `.ruff.toml` files. If Ruff detects
|
||||
multiple configuration files in the same directory, the `.ruff.toml` file will take precedence over
|
||||
the `ruff.toml` file, and the `ruff.toml` file will take precedence over the `pyproject.toml` file.
|
||||
# ...but use a different line length.
|
||||
line-length = 100
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
# Extend the `ruff.toml` file in the parent directory...
|
||||
extend = "../ruff.toml"
|
||||
|
||||
# ...but use a different line length.
|
||||
line-length = 100
|
||||
```
|
||||
|
||||
All of the above rules apply equivalently to `pyproject.toml`, `ruff.toml`, and `.ruff.toml` files.
|
||||
If Ruff detects multiple configuration files in the same directory, the `.ruff.toml` file will take
|
||||
precedence over the `ruff.toml` file, and the `ruff.toml` file will take precedence over
|
||||
the `pyproject.toml` file.
|
||||
|
||||
## Python file discovery
|
||||
|
||||
When passed a path on the command-line, Ruff will automatically discover all Python files in that
|
||||
path, taking into account the [`exclude`](settings.md#exclude) and [`extend-exclude`](settings.md#extend-exclude)
|
||||
settings in each directory's `pyproject.toml` file.
|
||||
settings in each directory's configuration file.
|
||||
|
||||
Files can also be selectively excluded from linting or formatting by scoping the `exclude` setting
|
||||
to the tool-specific configuration tables. For example, the following would prevent `ruff` from
|
||||
formatting `.pyi` files, but would continue to include them in linting:
|
||||
|
||||
```toml
|
||||
[tool.ruff.format]
|
||||
exclude = ["*.pyi"]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ruff.format]
|
||||
exclude = ["*.pyi"]
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
[format]
|
||||
exclude = ["*.pyi"]
|
||||
```
|
||||
|
||||
By default, Ruff will also skip any files that are omitted via `.ignore`, `.gitignore`,
|
||||
`.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](settings.md#respect-gitignore)).
|
||||
@@ -211,10 +306,18 @@ Ruff has built-in support for [Jupyter Notebooks](https://jupyter.org/).
|
||||
To opt in to linting and formatting Jupyter Notebook (`.ipynb`) files, add the `*.ipynb` pattern to
|
||||
your [`extend-include`](settings.md#extend-include) setting, like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-include = ["*.ipynb"]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-include = ["*.ipynb"]
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
extend-include = ["*.ipynb"]
|
||||
```
|
||||
|
||||
This will prompt Ruff to discover Jupyter Notebook (`.ipynb`) files in any specified
|
||||
directories, then lint and format them accordingly.
|
||||
@@ -245,10 +348,6 @@ Alternatively, pass the notebook file(s) to `ruff` on the command-line directly.
|
||||
`ruff check /path/to/notebook.ipynb` will always lint `notebook.ipynb`. Similarly,
|
||||
`ruff format /path/to/notebook.ipynb` will always format `notebook.ipynb`.
|
||||
|
||||
All of the above rules apply equivalently to `ruff.toml` and `.ruff.toml` files. If Ruff detects
|
||||
multiple configuration files in the same directory, the `.ruff.toml` file will take precedence over
|
||||
the `ruff.toml` file, and the `ruff.toml` file will take precedence over the `pyproject.toml` file.
|
||||
|
||||
## Command-line interface
|
||||
|
||||
Some configuration options can be provided via the command-line, such as those related to rule
|
||||
@@ -367,7 +466,7 @@ File selection:
|
||||
|
||||
Miscellaneous:
|
||||
-n, --no-cache
|
||||
Disable cache reads
|
||||
Disable cache reads [env: RUFF_NO_CACHE=]
|
||||
--isolated
|
||||
Ignore all configuration files
|
||||
--cache-dir <CACHE_DIR>
|
||||
@@ -414,7 +513,7 @@ Options:
|
||||
Print help
|
||||
|
||||
Miscellaneous:
|
||||
-n, --no-cache Disable cache reads
|
||||
-n, --no-cache Disable cache reads [env: RUFF_NO_CACHE=]
|
||||
--cache-dir <CACHE_DIR> Path to the cache directory [env: RUFF_CACHE_DIR=]
|
||||
--isolated Ignore all configuration files
|
||||
--stdin-filename <STDIN_FILENAME> The name of the file when passing it through stdin
|
||||
|
||||
170
docs/faq.md
170
docs/faq.md
@@ -265,24 +265,47 @@ them. You can find the supported settings in the [API reference](settings.md#iso
|
||||
For example, you can set [`known-first-party`](settings.md#known-first-party--isort-known-first-party-)
|
||||
like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
# Pyflakes
|
||||
"F",
|
||||
# Pycodestyle
|
||||
"E",
|
||||
"W",
|
||||
# isort
|
||||
"I001"
|
||||
]
|
||||
=== "pyproject.toml"
|
||||
|
||||
# Note: Ruff supports a top-level `src` option in lieu of isort's `src_paths` setting.
|
||||
src = ["src", "tests"]
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
# Pyflakes
|
||||
"F",
|
||||
# Pycodestyle
|
||||
"E",
|
||||
"W",
|
||||
# isort
|
||||
"I001"
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["my_module1", "my_module2"]
|
||||
```
|
||||
# Note: Ruff supports a top-level `src` option in lieu of isort's `src_paths` setting.
|
||||
src = ["src", "tests"]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["my_module1", "my_module2"]
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
[lint]
|
||||
select = [
|
||||
# Pyflakes
|
||||
"F",
|
||||
# Pycodestyle
|
||||
"E",
|
||||
"W",
|
||||
# isort
|
||||
"I001"
|
||||
]
|
||||
|
||||
# Note: Ruff supports a top-level `src` option in lieu of isort's `src_paths` setting.
|
||||
src = ["src", "tests"]
|
||||
|
||||
[lint.isort]
|
||||
known-first-party = ["my_module1", "my_module2"]
|
||||
```
|
||||
|
||||
## How does Ruff determine which of my imports are first-party, third-party, etc.?
|
||||
|
||||
@@ -315,24 +338,42 @@ the `--config` option, in which case, the current working directory is used as t
|
||||
In this case, Ruff would only check the top-level directory. Instead, we can configure Ruff to
|
||||
consider `src` as a first-party source like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# All paths are relative to the project root, which is the directory containing the pyproject.toml.
|
||||
src = ["src"]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
# All paths are relative to the project root, which is the directory containing the pyproject.toml.
|
||||
src = ["src"]
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
# All paths are relative to the project root, which is the directory containing the pyproject.toml.
|
||||
src = ["src"]
|
||||
```
|
||||
|
||||
If your `pyproject.toml`, `ruff.toml`, or `.ruff.toml` extends another configuration file, Ruff
|
||||
will still use the directory containing your `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file as
|
||||
the project root (as opposed to the directory of the file pointed to via the `extends` option).
|
||||
|
||||
For example, if you add a `ruff.toml` to the `tests` directory in the above example, you'll want to
|
||||
explicitly set the `src` option in the extended configuration file:
|
||||
For example, if you add a configuration file to the `tests` directory in the above example, you'll
|
||||
want to explicitly set the `src` option in the extended configuration file:
|
||||
|
||||
```toml
|
||||
# tests/ruff.toml
|
||||
extend = "../pyproject.toml"
|
||||
src = ["../src"]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend = "../pyproject.toml"
|
||||
src = ["../src"]
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
extend = "../pyproject.toml"
|
||||
src = ["../src"]
|
||||
```
|
||||
|
||||
Beyond this `src`-based detection, Ruff will also attempt to determine the current Python package
|
||||
for a given Python file, and mark imports from within the same package as first-party. For example,
|
||||
@@ -349,10 +390,18 @@ Ruff has built-in support for linting [Jupyter Notebooks](https://jupyter.org/).
|
||||
To opt in to linting Jupyter Notebook (`.ipynb`) files, add the `*.ipynb` pattern to your
|
||||
[`extend-include`](settings.md#extend-include) setting, like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-include = ["*.ipynb"]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
extend-include = ["*.ipynb"]
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
extend-include = ["*.ipynb"]
|
||||
```
|
||||
|
||||
This will prompt Ruff to discover Jupyter Notebook (`.ipynb`) files in any specified
|
||||
directories, then lint and format them accordingly.
|
||||
@@ -378,12 +427,21 @@ Found 3 errors.
|
||||
## Does Ruff support NumPy- or Google-style docstrings?
|
||||
|
||||
Yes! To enforce a docstring convention, add a [`convention`](settings.md#convention--pydocstyle-convention-)
|
||||
setting following to your `pyproject.toml`:
|
||||
setting following to your configuration file:
|
||||
|
||||
```toml
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google" # Accepts: "google", "numpy", or "pep257".
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google" # Accepts: "google", "numpy", or "pep257".
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
[lint.pydocstyle]
|
||||
convention = "google" # Accepts: "google", "numpy", or "pep257".
|
||||
```
|
||||
|
||||
For example, if you're coming from flake8-docstrings, and your originating configuration uses
|
||||
`--docstring-convention=numpy`, you'd instead set `convention = "numpy"` in your `pyproject.toml`,
|
||||
@@ -392,15 +450,29 @@ as above.
|
||||
Alongside [`convention`](settings.md#convention--pydocstyle-convention-), you'll want to
|
||||
explicitly enable the `D` rule code prefix, since the `D` rules are not enabled by default:
|
||||
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"D",
|
||||
]
|
||||
=== "pyproject.toml"
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
```
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"D",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
[lint]
|
||||
select = [
|
||||
"D",
|
||||
]
|
||||
|
||||
[lint.pydocstyle]
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
Setting a [`convention`](settings.md#convention--pydocstyle-convention-) force-disables any rules
|
||||
that are incompatible with that convention, no matter how they're provided, which avoids accidental
|
||||
@@ -419,11 +491,11 @@ Run `ruff check /path/to/code.py --show-settings` to view the resolved settings
|
||||
|
||||
## I want to use Ruff, but I don't want to use `pyproject.toml`. What are my options?
|
||||
|
||||
Yes! In lieu of a `pyproject.toml` file, you can use a `ruff.toml` file for configuration. The two
|
||||
In lieu of a `pyproject.toml` file, you can use a `ruff.toml` file for configuration. The two
|
||||
files are functionally equivalent and have an identical schema, with the exception that a `ruff.toml`
|
||||
file can omit the `[tool.ruff]` section header.
|
||||
file can omit the `[tool.ruff]` section header. For example:
|
||||
|
||||
For example, given this `pyproject.toml`:
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
@@ -433,7 +505,7 @@ line-length = 88
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
You could instead use a `ruff.toml` file like so:
|
||||
=== "ruff.toml"
|
||||
|
||||
```toml
|
||||
line-length = 88
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user