Compare commits
75 Commits
thin-lto
...
zanie/S608
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a19e98a2c7 | ||
|
|
2424188bb2 | ||
|
|
a59172528c | ||
|
|
cd29761b9c | ||
|
|
4ac78d5725 | ||
|
|
2083352ae3 | ||
|
|
0e2ece5217 | ||
|
|
a783b14e7d | ||
|
|
9d76e4e0b9 | ||
|
|
561277925f | ||
|
|
1074324c52 | ||
|
|
4099b9610f | ||
|
|
f7d249ae06 | ||
|
|
bf2cc3f520 | ||
|
|
23c819b4b3 | ||
|
|
16060670b8 | ||
|
|
534fc34f11 | ||
|
|
df9ade7fd9 | ||
|
|
345e1401cf | ||
|
|
a8e0d4ab4f | ||
|
|
6f23bdb78f | ||
|
|
d574fcd1ac | ||
|
|
3592f44ade | ||
|
|
8984072df2 | ||
|
|
6a6de53722 | ||
|
|
5ba852a878 | ||
|
|
c4fc2b8584 | ||
|
|
1c5f2288ba | ||
|
|
62f1830898 | ||
|
|
abca0a86ea | ||
|
|
213d315373 | ||
|
|
7fd95e15d9 | ||
|
|
02946e7b0c | ||
|
|
cbd9157bbf | ||
|
|
70f491d31e | ||
|
|
776eb8724f | ||
|
|
5f78580775 | ||
|
|
4d301f6dcc | ||
|
|
96b265ccec | ||
|
|
e0a0ddcf7d | ||
|
|
9724dfd939 | ||
|
|
d7144d6d8e | ||
|
|
39728a1198 | ||
|
|
8207d6df82 | ||
|
|
c8edac9d2b | ||
|
|
5a1a8bebca | ||
|
|
3e00ddce38 | ||
|
|
a7dbe9d670 | ||
|
|
036b6bc0bd | ||
|
|
d5606b7705 | ||
|
|
7968e190dd | ||
|
|
346a828db2 | ||
|
|
0ac124acef | ||
|
|
4ebd0bd31e | ||
|
|
565ddebb15 | ||
|
|
9d167a1f5c | ||
|
|
9e184a9067 | ||
|
|
9d1027c239 | ||
|
|
f499f0ca60 | ||
|
|
722687ad72 | ||
|
|
4760af3dcb | ||
|
|
4fdf97a95c | ||
|
|
0ea1076f85 | ||
|
|
3956f38999 | ||
|
|
fe9727ac38 | ||
|
|
3ebaca5246 | ||
|
|
7391f74cbc | ||
|
|
71e93a9fa4 | ||
|
|
e2c7b1ece6 | ||
|
|
621e98f452 | ||
|
|
0126f74c29 | ||
|
|
fce9f63418 | ||
|
|
ce549e75bc | ||
|
|
03303a9edd | ||
|
|
7873ca38e5 |
29
.github/release.yml
vendored
29
.github/release.yml
vendored
@@ -1,29 +0,0 @@
|
||||
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- internal
|
||||
- documentation
|
||||
categories:
|
||||
- title: Breaking Changes
|
||||
labels:
|
||||
- breaking
|
||||
- title: Rules
|
||||
labels:
|
||||
- rule
|
||||
- title: Settings
|
||||
labels:
|
||||
- configuration
|
||||
- cli
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- title: Formatter
|
||||
labels:
|
||||
- formatter
|
||||
- title: Preview
|
||||
labels:
|
||||
- preview
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -392,7 +392,7 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
- name: "Build docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
check-formatter-instability-and-black-similarity:
|
||||
name: "formatter instabilities and black similarity"
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.insiders.yml
|
||||
- name: "Build docs"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
|
||||
run: mkdocs build --strict -f mkdocs.generated.yml
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.3.2
|
||||
|
||||
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,5 +1,54 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bandit`\] Implement `mako-templates` (`S702`) ([#8533](https://github.com/astral-sh/ruff/pull/8533))
|
||||
- \[`flake8-trio`\] Implement `TRIO105` ([#8490](https://github.com/astral-sh/ruff/pull/8490))
|
||||
- \[`flake8-trio`\] Implement `TRIO109` ([#8534](https://github.com/astral-sh/ruff/pull/8534))
|
||||
- \[`flake8-trio`\] Implement `TRIO110` ([#8537](https://github.com/astral-sh/ruff/pull/8537))
|
||||
- \[`flake8-trio`\] Implement `TRIO115` ([#8486](https://github.com/astral-sh/ruff/pull/8486))
|
||||
- \[`refurb`\] Implement `type-none-comparison` (`FURB169`) ([#8487](https://github.com/astral-sh/ruff/pull/8487))
|
||||
- Flag all comparisons against builtin types in `E721` ([#8491](https://github.com/astral-sh/ruff/pull/8491))
|
||||
- Make `SIM118` fix as safe when the expression is a known dictionary ([#8525](https://github.com/astral-sh/ruff/pull/8525))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Fix multiline lambda expression statement formatting ([#8466](https://github.com/astral-sh/ruff/pull/8466))
|
||||
|
||||
### CLI
|
||||
|
||||
- Add hidden `--extension` to override inference of source type from file extension ([#8373](https://github.com/astral-sh/ruff/pull/8373))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Account for selector specificity when merging `extend_unsafe_fixes` and `override extend_safe_fixes` ([#8444](https://github.com/astral-sh/ruff/pull/8444))
|
||||
- Add support for disabling cache with `RUFF_NO_CACHE` environment variable ([#8538](https://github.com/astral-sh/ruff/pull/8538))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`E721`\] Flag comparisons to `memoryview` ([#8485](https://github.com/astral-sh/ruff/pull/8485))
|
||||
- Allow collapsed-ellipsis bodies in other statements ([#8499](https://github.com/astral-sh/ruff/pull/8499))
|
||||
- Avoid `D301` autofix for `u` prefixed strings ([#8495](https://github.com/astral-sh/ruff/pull/8495))
|
||||
- Only flag `flake8-trio` rules when `trio` import is present ([#8550](https://github.com/astral-sh/ruff/pull/8550))
|
||||
- Reject more syntactically invalid Python programs ([#8524](https://github.com/astral-sh/ruff/pull/8524))
|
||||
- Avoid raising `TRIO115` violations for `trio.sleep(...)` calls with non-number values ([#8532](https://github.com/astral-sh/ruff/pull/8532))
|
||||
- Fix `F841` false negative on assignment to multiple variables ([#8489](https://github.com/astral-sh/ruff/pull/8489))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix link to isort `known-first-party` ([#8562](https://github.com/astral-sh/ruff/pull/8562))
|
||||
- Add notes on fix safety to a few rules ([#8500](https://github.com/astral-sh/ruff/pull/8500))
|
||||
- Add missing toml config tabs ([#8512](https://github.com/astral-sh/ruff/pull/8512))
|
||||
- Add instructions for configuration of Emacs ([#8488](https://github.com/astral-sh/ruff/pull/8488))
|
||||
- Improve detail link contrast in dark mode ([#8548](https://github.com/astral-sh/ruff/pull/8548))
|
||||
- Fix typo in example ([#8506](https://github.com/astral-sh/ruff/pull/8506))
|
||||
- Added tabs for configuration files in the documentation ([#8480](https://github.com/astral-sh/ruff/pull/8480))
|
||||
- Recommend `project.requires-python` over `target-version` ([#8513](https://github.com/astral-sh/ruff/pull/8513))
|
||||
- Add singleton escape hatch to `B008` documentation ([#8501](https://github.com/astral-sh/ruff/pull/8501))
|
||||
- Fix tab configuration docs ([#8502](https://github.com/astral-sh/ruff/pull/8502))
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Preview features
|
||||
|
||||
@@ -315,9 +315,18 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
|
||||
### Creating a new release
|
||||
|
||||
1. Update the version with `rg 0.0.269 --files-with-matches | xargs sed -i 's/0.0.269/0.0.270/g'`
|
||||
1. Update `BREAKING_CHANGES.md`
|
||||
1. Create a PR with the version and `BREAKING_CHANGES.md` updated
|
||||
We use an experimental in-house tool for managing releases.
|
||||
|
||||
1. Install `rooster`: `pip install git+https://github.com/zanieb/rooster@main`
|
||||
1. Run `rooster release`; this command will:
|
||||
- Generate a changelog entry in `CHANGELOG.md`
|
||||
- Update versions in `pyproject.toml` and `Cargo.toml`
|
||||
- Update references to versions in the `README.md` and documentation
|
||||
1. The changelog should then be editorialized for consistency
|
||||
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||
1. Highlight any breaking changes in `BREAKING_CHANGES.md`
|
||||
1. Create a pull request with the changelog and version updates
|
||||
1. Merge the PR
|
||||
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
|
||||
main has your merged PR as last commit
|
||||
@@ -330,7 +339,11 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
|
||||
1. Attach artifacts to draft GitHub release
|
||||
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
|
||||
downstream jobs manually if needed.
|
||||
1. Create release notes in GitHub UI and promote from draft.
|
||||
1. Publish the GitHub release
|
||||
1. Open the draft release in the GitHub release section
|
||||
1. Copy the changelog for the release into the GitHub release
|
||||
- See previous releases for formatting of section headers
|
||||
1. Generate the contributor list with `rooster contributors` and add to the release notes
|
||||
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
|
||||
1. If needed, update the `ruff-lsp` and `ruff-vscode` repositories.
|
||||
|
||||
|
||||
98
Cargo.lock
generated
98
Cargo.lock
generated
@@ -64,9 +64,9 @@ checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36"
|
||||
checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"yansi-term",
|
||||
@@ -278,9 +278,7 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
@@ -810,7 +808,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -829,7 +827,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"toml",
|
||||
"toml 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -859,9 +857,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "2.9.0"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541"
|
||||
checksum = "fb5fd9bcbe8b1087cbd395b51498c01bc997cef73e778a80b77a811af5e2d29f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
@@ -927,9 +928,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -1033,12 +1034,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
"hashbrown 0.14.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1801,36 +1802,37 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0774c13ff0b8b7ebb4791c050c497aefcfe3f6a222c0829c7017161ed38391ff"
|
||||
checksum = "46d4a5e69187f23a29f8aa0ea57491d104ba541bc55f76552c2a74962aa20e04"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"toml",
|
||||
"toml 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.3.3"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bf780b59d590c25f8c59b44c124166a2a93587868b619fb8f5b47fb15e9ed6d"
|
||||
checksum = "1b9599bffc2cd7511355996e0cfd979266b2cfa3f3ff5247d07a3a6e1ded6158"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap",
|
||||
"nextest-workspace-hack",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.29.0"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -2060,9 +2062,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"annotate-snippets 0.9.2",
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"assert_cmd",
|
||||
@@ -2152,7 +2154,7 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.7.8",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
@@ -2196,10 +2198,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.1",
|
||||
"annotate-snippets 0.9.2",
|
||||
"anyhow",
|
||||
"bitflags 2.4.1",
|
||||
"chrono",
|
||||
@@ -2252,7 +2254,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"toml 0.7.8",
|
||||
"typed-arena",
|
||||
"unicode-width",
|
||||
"unicode_names2",
|
||||
@@ -2447,7 +2449,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2538,7 +2540,7 @@ dependencies = [
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"toml 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2802,9 +2804,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.1"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
@@ -2831,6 +2833,15 @@ dependencies = [
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa"
|
||||
dependencies = [
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -3086,7 +3097,19 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
"toml_edit 0.19.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.20.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3111,6 +3134,19 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
|
||||
@@ -38,7 +38,7 @@ serde = { version = "1.0.190", features = ["derive"] }
|
||||
serde_json = { version = "1.0.108" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.3.0", features = ["inline"] }
|
||||
smallvec = { version = "1.11.1" }
|
||||
smallvec = { version = "1.11.2" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.3" }
|
||||
@@ -56,7 +56,7 @@ uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev.package.insta]
|
||||
|
||||
24
README.md
24
README.md
@@ -54,7 +54,7 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
- [Pandas](https://github.com/pandas-dev/pandas)
|
||||
- [SciPy](https://github.com/scipy/scipy)
|
||||
|
||||
...and many more.
|
||||
...and [many more](#whos-using-ruff).
|
||||
|
||||
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
|
||||
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
@@ -150,11 +150,12 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.4
|
||||
rev: v0.1.5
|
||||
hooks:
|
||||
# Run the Ruff linter.
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
# Run the Ruff formatter.
|
||||
args: [ --fix ]
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
```
|
||||
|
||||
@@ -376,8 +377,8 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
|
||||
- [Apache Airflow](https://github.com/apache/airflow)
|
||||
- AstraZeneca ([Magnus](https://github.com/AstraZeneca/magnus-core))
|
||||
- Benchling ([Refac](https://github.com/benchling/refac))
|
||||
- [Babel](https://github.com/python-babel/babel)
|
||||
- Benchling ([Refac](https://github.com/benchling/refac))
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- [DVC](https://github.com/iterative/dvc)
|
||||
@@ -388,15 +389,16 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Gradio](https://github.com/gradio-app/gradio)
|
||||
- [Great Expectations](https://github.com/great-expectations/great_expectations)
|
||||
- [HTTPX](https://github.com/encode/httpx)
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Home Assistant](https://github.com/home-assistant/core)
|
||||
- Hugging Face ([Transformers](https://github.com/huggingface/transformers),
|
||||
[Datasets](https://github.com/huggingface/datasets),
|
||||
[Diffusers](https://github.com/huggingface/diffusers))
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Home Assistant](https://github.com/home-assistant/core)
|
||||
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
|
||||
- [Ibis](https://github.com/ibis-project/ibis)
|
||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||
- [LangChain](https://github.com/hwchase17/langchain)
|
||||
- [Litestar](https://litestar.dev/)
|
||||
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
|
||||
- Matrix ([Synapse](https://github.com/matrix-org/synapse))
|
||||
- [MegaLinter](https://github.com/oxsecurity/megalinter)
|
||||
@@ -421,20 +423,20 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [PostHog](https://github.com/PostHog/posthog)
|
||||
- Prefect ([Python SDK](https://github.com/PrefectHQ/prefect), [Marvin](https://github.com/PrefectHQ/marvin))
|
||||
- [PyInstaller](https://github.com/pyinstaller/pyinstaller)
|
||||
- [PyMC-Marketing](https://github.com/pymc-labs/pymc-marketing)
|
||||
- [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)
|
||||
- Scale AI ([Launch SDK](https://github.com/scaleapi/launch-python-client))
|
||||
- Snowflake ([SnowCLI](https://github.com/Snowflake-Labs/snowcli))
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
- Scale AI ([Launch SDK](https://github.com/scaleapi/launch-python-client))
|
||||
- [SciPy](https://github.com/scipy/scipy)
|
||||
- Snowflake ([SnowCLI](https://github.com/Snowflake-Labs/snowcli))
|
||||
- [Sphinx](https://github.com/sphinx-doc/sphinx)
|
||||
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
|
||||
- [Litestar](https://litestar.dev/)
|
||||
- [Starlette](https://github.com/encode/starlette)
|
||||
- [The Algorithms](https://github.com/TheAlgorithms/Python)
|
||||
- [Vega-Altair](https://github.com/altair-viz/altair)
|
||||
- WordPress ([Openverse](https://github.com/WordPress/openverse))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -28,7 +28,7 @@ ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_workspace = { path = "../ruff_workspace" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { workspace = true }
|
||||
argfile = { version = "0.1.6" }
|
||||
bincode = { version = "1.3.3" }
|
||||
|
||||
413
crates/ruff_cli/resources/test/fixtures/trailing_semicolon.ipynb
vendored
Normal file
413
crates/ruff_cli/resources/test/fixtures/trailing_semicolon.ipynb
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "4f8ce941-1492-4d4e-8ab5-70d733fe891a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%config ZMQInteractiveShell.ast_node_interactivity=\"last_expr_or_assign\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "721ec705-0c65-4bfb-9809-7ed8bc534186",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Assignment statement without a semicolon\n",
|
||||
"x = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "de50e495-17e5-41cc-94bd-565757555d7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Assignment statement with a semicolon\n",
|
||||
"x = 1;\n",
|
||||
"x = 1;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "39e31201-23da-44eb-8684-41bba3663991",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Augmented assignment without a semicolon\n",
|
||||
"x += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "6b73d3dd-c73a-4697-9e97-e109a6c1fbab",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Augmented assignment without a semicolon\n",
|
||||
"x += 1;\n",
|
||||
"x += 1; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "2a3e5b86-aa5b-46ba-b9c6-0386d876f58c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Multiple assignment without a semicolon\n",
|
||||
"x = y = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "07f89e51-9357-4cfb-8fc5-76fb75e35949",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Multiple assignment with a semicolon\n",
|
||||
"x = y = 1;\n",
|
||||
"x = y = 1;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "c22b539d-473e-48f8-a236-625e58c47a00",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Tuple unpacking without a semicolon\n",
|
||||
"x, y = 1, 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "12c87940-a0d5-403b-a81c-7507eb06dc7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Tuple unpacking with a semicolon (irrelevant)\n",
|
||||
"x, y = 1, 2;\n",
|
||||
"x, y = 1, 2; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "5a768c76-6bc4-470c-b37e-8cc14bc6caf4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Annotated assignment statement without a semicolon\n",
|
||||
"x: int = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "21bfda82-1a9a-4ba1-9078-74ac480804b5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Annotated assignment statement without a semicolon\n",
|
||||
"x: int = 1;\n",
|
||||
"x: int = 1; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "09929999-ff29-4d10-ad2b-e665af15812d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Assignment expression without a semicolon\n",
|
||||
"(x := 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "32a83217-1bad-4f61-855e-ffcdb119c763",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Assignment expression with a semicolon\n",
|
||||
"(x := 1);\n",
|
||||
"(x := 1); # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "61b81865-277e-4964-b03e-eb78f1f318eb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"# Expression without a semicolon\n",
|
||||
"x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "974c29be-67e1-4000-95fa-6ca118a63bad",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"# Expression with a semicolon\n",
|
||||
"x;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "cfeb1757-46d6-4f13-969f-a283b6d0304f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Point:\n",
|
||||
" def __init__(self, x, y):\n",
|
||||
" self.x = x\n",
|
||||
" self.y = y\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"p = Point(0, 0);"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "2ee7f1a5-ccfe-4004-bfa4-ef834a58da97",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Assignment statement where the left is an attribute access doesn't\n",
|
||||
"# print the value.\n",
|
||||
"p.x = 1;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"id": "3e49370a-048b-474d-aa0a-3d1d4a73ad37",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = {}\n",
|
||||
"\n",
|
||||
"# Neither does the subscript node\n",
|
||||
"data[\"foo\"] = 1;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"id": "d594bdd3-eaa9-41ef-8cda-cf01bc273b2d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if (x := 1):\n",
|
||||
" # It should be the top level statement\n",
|
||||
" x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"id": "e532f0cf-80c7-42b7-8226-6002fcf74fb6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Parentheses with comments\n",
|
||||
"(\n",
|
||||
" x := 1 # comment\n",
|
||||
") # comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"id": "473c5d62-871b-46ed-8a34-27095243f462",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Parentheses with comments\n",
|
||||
"(\n",
|
||||
" x := 1 # comment\n",
|
||||
"); # comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"id": "8c3c2361-f49f-45fe-bbe3-7e27410a8a86",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Hello world!'"
|
||||
]
|
||||
},
|
||||
"execution_count": 22,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\"\"\"Hello world!\"\"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"id": "23dbe9b5-3f68-4890-ab2d-ab0dbfd0712a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\"\"\"Hello world!\"\"\"; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"id": "3ce33108-d95d-4c70-83d1-0d4fd36a2951",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'x = 1'"
|
||||
]
|
||||
},
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"f\"x = {x}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"id": "654a4a67-de43-4684-824a-9451c67db48f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"f\"x = {x}\";\n",
|
||||
"f\"x = {x}\"; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff-playground)",
|
||||
"language": "python",
|
||||
"name": "ruff-playground"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -8,8 +8,8 @@ use ruff_linter::line_width::LineLength;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
|
||||
UnsafeFixes,
|
||||
ExtensionPair, FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion,
|
||||
SerializationFormat, UnsafeFixes,
|
||||
};
|
||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
@@ -351,6 +351,9 @@ pub struct CheckCommand {
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub show_settings: bool,
|
||||
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]).
|
||||
#[arg(long, value_delimiter = ',', hide = true)]
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
/// Dev-only argument to show fixes
|
||||
#[arg(long, hide = true)]
|
||||
pub ecosystem_ci: bool,
|
||||
@@ -535,6 +538,7 @@ impl CheckCommand {
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
output_format: self.output_format,
|
||||
show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes),
|
||||
extension: self.extension,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -647,6 +651,7 @@ pub struct CliOverrides {
|
||||
pub force_exclude: Option<bool>,
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub show_fixes: Option<bool>,
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
}
|
||||
|
||||
impl ConfigurationTransformer for CliOverrides {
|
||||
@@ -731,6 +736,9 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
if let Some(target_version) = &self.target_version {
|
||||
config.target_version = Some(*target_version);
|
||||
}
|
||||
if let Some(extension) = &self.extension {
|
||||
config.lint.extension = Some(extension.clone().into_iter().collect());
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::diagnostics::Diagnostics;
|
||||
use crate::panic::catch_unwind;
|
||||
|
||||
/// Run the linter over a collection of files.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check(
|
||||
files: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
@@ -184,6 +185,7 @@ pub(crate) fn check(
|
||||
|
||||
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
||||
/// a diagnostic if the linting the file panics.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn lint_path(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectCo
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||
use crate::stdin::read_from_stdin;
|
||||
use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
|
||||
/// Run the linter over a single file, read from `stdin`.
|
||||
pub(crate) fn check_stdin(
|
||||
@@ -21,6 +21,9 @@ pub(crate) fn check_stdin(
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
if let Some(filename) = filename {
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
if fix_mode.is_apply() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
|
||||
@@ -29,14 +32,17 @@ pub(crate) fn check_stdin(
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
||||
{
|
||||
if fix_mode.is_apply() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
let stdin = read_from_stdin()?;
|
||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||
packaging::detect_package_root(path, &pyproject_config.settings.linter.namespace_packages)
|
||||
});
|
||||
let stdin = read_from_stdin()?;
|
||||
let mut diagnostics = lint_stdin(
|
||||
filename,
|
||||
package_root,
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::commands::format::{
|
||||
FormatResult, FormattedSource,
|
||||
};
|
||||
use crate::resolve::resolve;
|
||||
use crate::stdin::read_from_stdin;
|
||||
use crate::stdin::{parrot_stdin, read_from_stdin};
|
||||
use crate::ExitStatus;
|
||||
|
||||
/// Run the formatter over a single file, read from `stdin`.
|
||||
@@ -34,6 +34,9 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
if pyproject_config.settings.file_resolver.force_exclude {
|
||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
@@ -42,6 +45,9 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
||||
{
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +56,9 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
let path = cli.stdin_filename.as_deref();
|
||||
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::registry::AsRule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::types::{ExtensionMapping, UnsafeFixes};
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{fs, IOError, SyntaxError};
|
||||
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::{SourceType, TomlSourceType};
|
||||
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||
use ruff_source_file::{LineIndex, SourceCode, SourceFileBuilder};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::Settings;
|
||||
@@ -177,6 +177,11 @@ impl AddAssign for FixMap {
|
||||
}
|
||||
}
|
||||
|
||||
fn override_source_type(path: Option<&Path>, extension: &ExtensionMapping) -> Option<PySourceType> {
|
||||
let ext = path?.extension()?.to_str()?;
|
||||
extension.get(ext).map(PySourceType::from)
|
||||
}
|
||||
|
||||
/// Lint the source code at the given `Path`.
|
||||
pub(crate) fn lint_path(
|
||||
path: &Path,
|
||||
@@ -221,31 +226,35 @@ pub(crate) fn lint_path(
|
||||
|
||||
debug!("Checking: {}", path.display());
|
||||
|
||||
let source_type = match SourceType::from(path) {
|
||||
SourceType::Toml(TomlSourceType::Pyproject) => {
|
||||
let messages = if settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
|
||||
{
|
||||
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
|
||||
Ok(contents) => contents,
|
||||
Err(err) => {
|
||||
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
|
||||
}
|
||||
let source_type = match override_source_type(Some(path), &settings.extension) {
|
||||
Some(source_type) => source_type,
|
||||
None => match SourceType::from(path) {
|
||||
SourceType::Toml(TomlSourceType::Pyproject) => {
|
||||
let messages = if settings
|
||||
.rules
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
|
||||
{
|
||||
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
|
||||
Ok(contents) => contents,
|
||||
Err(err) => {
|
||||
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
|
||||
}
|
||||
};
|
||||
let source_file =
|
||||
SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
||||
lint_pyproject_toml(source_file, settings)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
||||
lint_pyproject_toml(source_file, settings)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
return Ok(Diagnostics {
|
||||
messages,
|
||||
..Diagnostics::default()
|
||||
});
|
||||
}
|
||||
SourceType::Toml(_) => return Ok(Diagnostics::default()),
|
||||
SourceType::Python(source_type) => source_type,
|
||||
return Ok(Diagnostics {
|
||||
messages,
|
||||
..Diagnostics::default()
|
||||
});
|
||||
}
|
||||
SourceType::Toml(_) => return Ok(Diagnostics::default()),
|
||||
SourceType::Python(source_type) => source_type,
|
||||
},
|
||||
};
|
||||
|
||||
// Extract the sources from the file.
|
||||
@@ -370,8 +379,15 @@ pub(crate) fn lint_stdin(
|
||||
fix_mode: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// TODO(charlie): Support `pyproject.toml`.
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
return Ok(Diagnostics::default());
|
||||
let source_type = if let Some(source_type) =
|
||||
override_source_type(path, &settings.linter.extension)
|
||||
{
|
||||
source_type
|
||||
} else {
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
return Ok(Diagnostics::default());
|
||||
};
|
||||
source_type
|
||||
};
|
||||
|
||||
// Extract the sources from the file.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
/// Read a string from `stdin`.
|
||||
pub(crate) fn read_from_stdin() -> Result<String, io::Error> {
|
||||
@@ -7,3 +7,11 @@ pub(crate) fn read_from_stdin() -> Result<String, io::Error> {
|
||||
io::stdin().lock().read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Read bytes from `stdin` and write them to `stdout`.
|
||||
pub(crate) fn parrot_stdin() -> Result<(), io::Error> {
|
||||
let mut buffer = String::new();
|
||||
io::stdin().lock().read_to_string(&mut buffer)?;
|
||||
io::stdout().write_all(buffer.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -320,6 +320,11 @@ if __name__ == '__main__':
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == '__main__':
|
||||
say_hy("dear Ruff contributor")
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
@@ -813,3 +818,432 @@ fn test_diff_stdin_formatted() {
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notebook_trailing_semicolon() {
|
||||
let fixtures = Path::new("resources").join("test").join("fixtures");
|
||||
let unformatted = fs::read(fixtures.join("trailing_semicolon.ipynb")).unwrap();
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.ipynb"])
|
||||
.arg("-")
|
||||
.pass_stdin(unformatted), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "4f8ce941-1492-4d4e-8ab5-70d733fe891a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%config ZMQInteractiveShell.ast_node_interactivity=\"last_expr_or_assign\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "721ec705-0c65-4bfb-9809-7ed8bc534186",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Assignment statement without a semicolon\n",
|
||||
"x = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "de50e495-17e5-41cc-94bd-565757555d7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Assignment statement with a semicolon\n",
|
||||
"x = 1\n",
|
||||
"x = 1;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "39e31201-23da-44eb-8684-41bba3663991",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Augmented assignment without a semicolon\n",
|
||||
"x += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "6b73d3dd-c73a-4697-9e97-e109a6c1fbab",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Augmented assignment without a semicolon\n",
|
||||
"x += 1\n",
|
||||
"x += 1; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "2a3e5b86-aa5b-46ba-b9c6-0386d876f58c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Multiple assignment without a semicolon\n",
|
||||
"x = y = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "07f89e51-9357-4cfb-8fc5-76fb75e35949",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Multiple assignment with a semicolon\n",
|
||||
"x = y = 1\n",
|
||||
"x = y = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "c22b539d-473e-48f8-a236-625e58c47a00",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Tuple unpacking without a semicolon\n",
|
||||
"x, y = 1, 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "12c87940-a0d5-403b-a81c-7507eb06dc7e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Tuple unpacking with a semicolon (irrelevant)\n",
|
||||
"x, y = 1, 2\n",
|
||||
"x, y = 1, 2 # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "5a768c76-6bc4-470c-b37e-8cc14bc6caf4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Annotated assignment statement without a semicolon\n",
|
||||
"x: int = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "21bfda82-1a9a-4ba1-9078-74ac480804b5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Annotated assignment statement without a semicolon\n",
|
||||
"x: int = 1\n",
|
||||
"x: int = 1; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "09929999-ff29-4d10-ad2b-e665af15812d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Assignment expression without a semicolon\n",
|
||||
"(x := 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "32a83217-1bad-4f61-855e-ffcdb119c763",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Assignment expression with a semicolon\n",
|
||||
"(x := 1)\n",
|
||||
"(x := 1); # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "61b81865-277e-4964-b03e-eb78f1f318eb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"# Expression without a semicolon\n",
|
||||
"x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "974c29be-67e1-4000-95fa-6ca118a63bad",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"# Expression with a semicolon\n",
|
||||
"x;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "cfeb1757-46d6-4f13-969f-a283b6d0304f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Point:\n",
|
||||
" def __init__(self, x, y):\n",
|
||||
" self.x = x\n",
|
||||
" self.y = y\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"p = Point(0, 0);"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"id": "2ee7f1a5-ccfe-4004-bfa4-ef834a58da97",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Assignment statement where the left is an attribute access doesn't\n",
|
||||
"# print the value.\n",
|
||||
"p.x = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"id": "3e49370a-048b-474d-aa0a-3d1d4a73ad37",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = {}\n",
|
||||
"\n",
|
||||
"# Neither does the subscript node\n",
|
||||
"data[\"foo\"] = 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"id": "d594bdd3-eaa9-41ef-8cda-cf01bc273b2d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if x := 1:\n",
|
||||
" # It should be the top level statement\n",
|
||||
" x"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"id": "e532f0cf-80c7-42b7-8226-6002fcf74fb6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Parentheses with comments\n",
|
||||
"(\n",
|
||||
" x := 1 # comment\n",
|
||||
") # comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"id": "473c5d62-871b-46ed-8a34-27095243f462",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Parentheses with comments\n",
|
||||
"(\n",
|
||||
" x := 1 # comment\n",
|
||||
"); # comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"id": "8c3c2361-f49f-45fe-bbe3-7e27410a8a86",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Hello world!'"
|
||||
]
|
||||
},
|
||||
"execution_count": 22,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\"\"\"Hello world!\"\"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"id": "23dbe9b5-3f68-4890-ab2d-ab0dbfd0712a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\"\"\"Hello world!\"\"\"; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"id": "3ce33108-d95d-4c70-83d1-0d4fd36a2951",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'x = 1'"
|
||||
]
|
||||
},
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"f\"x = {x}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"id": "654a4a67-de43-4684-824a-9451c67db48f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 1\n",
|
||||
"f\"x = {x}\"\n",
|
||||
"f\"x = {x}\"; # comment\n",
|
||||
"# comment"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff-playground)",
|
||||
"language": "python",
|
||||
"name": "ruff-playground"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -320,6 +320,119 @@ fn stdin_fix_jupyter() {
|
||||
Found 2 errors (2 fixed, 0 remaining).
|
||||
"###);
|
||||
}
|
||||
#[test]
|
||||
fn stdin_override_parser_ipynb() {
|
||||
let args = ["--extension", "py:ipynb", "--stdin-filename", "Jupyter.py"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(args)
|
||||
.pass_stdin(r#"{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "dccc687c-96e2-4604-b957-a8a89b5bec06",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19e1b029-f516-4662-a9b9-623b93edac1a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "cdce7b92-b0fb-4c02-86f6-e233b26fa84f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "e40b33d2-7fe4-46c5-bdf0-8802f3052565",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a1899bc8-d46f-4ec0-b1d1-e1ca0f04bf60",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Jupyter.py:cell 1:1:8: F401 [*] `os` imported but unused
|
||||
Jupyter.py:cell 3:1:8: F401 [*] `sys` imported but unused
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_override_parser_py() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--extension", "ipynb:python", "--stdin-filename", "F401.ipynb"])
|
||||
.pass_stdin("import os\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
F401.ipynb:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_fix_when_not_fixable_should_still_print_contents() {
|
||||
@@ -1407,3 +1520,106 @@ extend-safe-fixes = ["UP034"]
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_extend_unsafe_fixes_conflict_with_extend_safe_fixes_by_specificity() -> Result<()> {
|
||||
// Adding a rule to one option with a more specific selector should override the other option
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
target-version = "py310"
|
||||
[lint]
|
||||
extend-unsafe-fixes = ["UP", "UP034"]
|
||||
extend-safe-fixes = ["UP03"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.args([
|
||||
"--output-format",
|
||||
"text",
|
||||
"--no-cache",
|
||||
"--select",
|
||||
"F601,UP018,UP034,UP038",
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\nprint(str('foo'))\nisinstance(x, (int, str))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:14: F601 Dictionary key literal `'a'` repeated
|
||||
-:2:7: UP034 Avoid extraneous parentheses
|
||||
-:3:7: UP018 Unnecessary `str` call (rewrite as a literal)
|
||||
-:4:1: UP038 [*] Use `X | Y` in `isinstance` call instead of `(X, Y)`
|
||||
Found 4 errors.
|
||||
[*] 1 fixable with the `--fix` option (3 hidden fixes can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_docstring_conventions_overrides() -> Result<()> {
|
||||
// But if we explicitly select it, we override the convention
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.pydocstyle]
|
||||
convention = "numpy"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let stdin = r#"
|
||||
def log(x, base) -> float:
|
||||
"""Calculate natural log of a value
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x :
|
||||
Hello
|
||||
"""
|
||||
return math.log(x)
|
||||
"#;
|
||||
|
||||
// If we only select the prefix, then everything passes
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "-", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.args(["--output-format", "text", "--no-cache", "--select", "D41"])
|
||||
.pass_stdin(stdin),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
|
||||
// But if we select the exact code, we get an error
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "-", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.args(["--output-format", "text", "--no-cache", "--select", "D417"])
|
||||
.pass_stdin(stdin),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:2:5: D417 Missing argument description in the docstring for `log`: `base`
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -129,12 +129,12 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push_str("**Example usage**:\n\n");
|
||||
output.push_str(&format_tab(
|
||||
"pyproject.toml",
|
||||
&format_header(parent_set, ConfigurationFile::PyprojectToml),
|
||||
&format_header(field.scope, parent_set, ConfigurationFile::PyprojectToml),
|
||||
field.example,
|
||||
));
|
||||
output.push_str(&format_tab(
|
||||
"ruff.toml",
|
||||
&format_header(parent_set, ConfigurationFile::RuffToml),
|
||||
&format_header(field.scope, parent_set, ConfigurationFile::RuffToml),
|
||||
field.example,
|
||||
));
|
||||
output.push('\n');
|
||||
@@ -149,23 +149,53 @@ fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
/// Format the TOML header for the example usage for a given option.
|
||||
///
|
||||
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
|
||||
fn format_header(
|
||||
scope: Option<&str>,
|
||||
parent_set: &Set,
|
||||
configuration: ConfigurationFile,
|
||||
) -> String {
|
||||
match configuration {
|
||||
ConfigurationFile::PyprojectToml => format!("[tool.ruff{fmt}]"),
|
||||
ConfigurationFile::PyprojectToml => {
|
||||
let mut header = if let Some(set_name) = parent_set.name() {
|
||||
if set_name == "format" {
|
||||
String::from("tool.ruff.format")
|
||||
} else {
|
||||
format!("tool.ruff.lint.{set_name}")
|
||||
}
|
||||
} else {
|
||||
"tool.ruff".to_string()
|
||||
};
|
||||
if let Some(scope) = scope {
|
||||
if !header.is_empty() {
|
||||
header.push('.');
|
||||
}
|
||||
header.push_str(scope);
|
||||
}
|
||||
format!("[{header}]")
|
||||
}
|
||||
ConfigurationFile::RuffToml => {
|
||||
if fmt.is_empty() {
|
||||
let mut header = if let Some(set_name) = parent_set.name() {
|
||||
if set_name == "format" {
|
||||
String::from("format")
|
||||
} else {
|
||||
format!("lint.{set_name}")
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
if let Some(scope) = scope {
|
||||
if !header.is_empty() {
|
||||
header.push('.');
|
||||
}
|
||||
header.push_str(scope);
|
||||
}
|
||||
if header.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("[{}]", fmt.strip_prefix('.').unwrap())
|
||||
format!("[{header}]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::edit::Edit;
|
||||
pub enum Applicability {
|
||||
/// The fix is unsafe and should only be displayed for manual application by the user.
|
||||
/// The fix is likely to be incorrect or the resulting code may have invalid syntax.
|
||||
Display,
|
||||
DisplayOnly,
|
||||
|
||||
/// The fix is unsafe and should only be applied with user opt-in.
|
||||
/// The fix may be what the user intended, but it is uncertain; the resulting code will have valid syntax.
|
||||
@@ -87,22 +87,22 @@ impl Fix {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from an [`Edit`] element .
|
||||
pub fn display_edit(edit: Edit) -> Self {
|
||||
/// Create a new [`Fix`] that should only [display](Applicability::DisplayOnly) and not apply from an [`Edit`] element .
|
||||
pub fn display_only_edit(edit: Edit) -> Self {
|
||||
Self {
|
||||
edits: vec![edit],
|
||||
applicability: Applicability::Display,
|
||||
applicability: Applicability::DisplayOnly,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Fix`] that should only [display](Applicability::Display) and not apply from multiple [`Edit`] elements.
|
||||
pub fn display_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
/// Create a new [`Fix`] that should only [display](Applicability::DisplayOnly) and not apply from multiple [`Edit`] elements.
|
||||
pub fn display_only_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> 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: Applicability::Display,
|
||||
applicability: Applicability::DisplayOnly,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -30,7 +30,7 @@ ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
aho-corasick = { version = "1.1.2" }
|
||||
annotate-snippets = { version = "0.9.1", features = ["color"] }
|
||||
annotate-snippets = { version = "0.9.2", features = ["color"] }
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
@@ -53,8 +53,8 @@ path-absolutize = { workspace = true, features = [
|
||||
] }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.3.12", features = ["serde"] }
|
||||
pyproject-toml = { version = "0.8.0" }
|
||||
quick-junit = { version = "0.3.2" }
|
||||
pyproject-toml = { version = "0.8.1" }
|
||||
quick-junit = { version = "0.3.5" }
|
||||
regex = { workspace = true }
|
||||
result-like = { version = "0.4.6" }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
32
crates/ruff_linter/resources/test/fixtures/flake8_annotations/auto_return_type.py
vendored
Normal file
32
crates/ruff_linter/resources/test/fixtures/flake8_annotations/auto_return_type.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
def func():
|
||||
return 1
|
||||
|
||||
|
||||
def func():
|
||||
return 1.5
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x > 0:
|
||||
return 1
|
||||
else:
|
||||
return 1.5
|
||||
|
||||
|
||||
def func():
|
||||
return True
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x > 0:
|
||||
return None
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
def func(x: int):
|
||||
return 1 or 2.5 if x > 0 else 1.5 or "str"
|
||||
|
||||
|
||||
def func(x: int):
|
||||
return 1 + 2.5 if x > 0 else 1.5 or "str"
|
||||
@@ -101,4 +101,5 @@ query = "INSERT table VALUES (%s)" % (var,)
|
||||
query = "REPLACE INTO table VALUES (%s)" % (var,)
|
||||
query = "REPLACE table VALUES (%s)" % (var,)
|
||||
|
||||
query = "Deselect something that is not SQL even though it has a ' from ' somewhere in %s." % "there"
|
||||
not_a_query = "Deselect something that is not SQL even though it has a ' from ' somewhere in %s." % "there"
|
||||
not_a_query = f"Please select a value from the list of possible variants: {variants}."
|
||||
|
||||
9
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S702.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/flake8_bandit/S702.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from mako.template import Template
|
||||
from mako import template
|
||||
import mako
|
||||
|
||||
|
||||
Template("hello")
|
||||
|
||||
mako.template.Template("hern")
|
||||
template.Template("hern")
|
||||
@@ -91,3 +91,18 @@ class Registry:
|
||||
|
||||
def foo(self) -> None:
|
||||
object.__setattr__(self, "flag", True)
|
||||
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
pass
|
||||
|
||||
|
||||
def func(x: bool | str):
|
||||
pass
|
||||
|
||||
|
||||
def func(x: int | str):
|
||||
pass
|
||||
|
||||
@@ -639,3 +639,18 @@ foo = namedtuple(
|
||||
:20
|
||||
],
|
||||
)
|
||||
|
||||
# F-strings
|
||||
kwargs.pop("remove", f"this {trailing_comma}",)
|
||||
|
||||
raise Exception(
|
||||
"first", extra=f"Add trailing comma here ->"
|
||||
)
|
||||
|
||||
assert False, f"<- This is not a trailing comma"
|
||||
|
||||
f"""This is a test. {
|
||||
"Another sentence."
|
||||
if True else
|
||||
"Don't add a trailing comma here ->"
|
||||
}"""
|
||||
|
||||
@@ -58,3 +58,33 @@ def f_fix_indentation_check(foo):
|
||||
# Report these, but don't fix them
|
||||
if foo: raise RuntimeError("This is an example exception")
|
||||
if foo: x = 1; raise RuntimeError("This is an example exception")
|
||||
|
||||
|
||||
def f_triple_quoted_string():
|
||||
raise RuntimeError(f"""This is an {"example"} exception""")
|
||||
|
||||
|
||||
def f_multi_line_string():
|
||||
raise RuntimeError(
|
||||
"first"
|
||||
"second"
|
||||
)
|
||||
|
||||
|
||||
def f_multi_line_string2():
|
||||
raise RuntimeError(
|
||||
"This is an {example} exception".format(
|
||||
example="example"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def f_multi_line_string2():
|
||||
raise RuntimeError(
|
||||
(
|
||||
"This is an "
|
||||
"{example} exception"
|
||||
).format(
|
||||
example="example"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -148,3 +148,32 @@ for i in range(10):
|
||||
for i in range(10):
|
||||
pass # comment
|
||||
pass
|
||||
|
||||
|
||||
def foo():
|
||||
print("foo")
|
||||
...
|
||||
|
||||
|
||||
def foo():
|
||||
"""A docstring."""
|
||||
print("foo")
|
||||
...
|
||||
|
||||
|
||||
for i in range(10):
|
||||
...
|
||||
...
|
||||
|
||||
for i in range(10):
|
||||
...
|
||||
|
||||
...
|
||||
|
||||
for i in range(10):
|
||||
... # comment
|
||||
...
|
||||
|
||||
for i in range(10):
|
||||
...
|
||||
pass
|
||||
|
||||
@@ -38,3 +38,15 @@ class User:
|
||||
foo: bool = BooleanField()
|
||||
# ...
|
||||
bar = StringField() # PIE794
|
||||
|
||||
|
||||
class Person:
|
||||
name = "Foo"
|
||||
name = name + " Bar"
|
||||
name = "Bar" # PIE794
|
||||
|
||||
|
||||
class Person:
|
||||
name: str = "Foo"
|
||||
name: str = name + " Bar"
|
||||
name: str = "Bar" # PIE794
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
{"foo": 1, **{"bar": 1}} # PIE800
|
||||
|
||||
{**{"bar": 10}, "a": "b"} # PIE800
|
||||
|
||||
foo({**foo, **{"bar": True}}) # PIE800
|
||||
|
||||
{**foo, **{"bar": 10}} # PIE800
|
||||
|
||||
{ # PIE800
|
||||
"a": "b",
|
||||
# Preserve
|
||||
**{
|
||||
# all
|
||||
"bar": 10, # the
|
||||
# comments
|
||||
},
|
||||
}
|
||||
|
||||
{**foo, **buzz, **{bar: 10}} # PIE800
|
||||
|
||||
{**foo, "bar": True } # OK
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
@dataclass
|
||||
class Foo:
|
||||
foo: List[str] = field(default_factory=lambda: []) # PIE807
|
||||
bar: Dict[str, int] = field(default_factory=lambda: {}) # PIE807
|
||||
|
||||
|
||||
class FooTable(BaseTable):
|
||||
bar = fields.ListField(default=lambda: []) # PIE807
|
||||
foo = fields.ListField(default=lambda: []) # PIE807
|
||||
bar = fields.ListField(default=lambda: {}) # PIE807
|
||||
|
||||
|
||||
class FooTable(BaseTable):
|
||||
bar = fields.ListField(lambda: []) # PIE807
|
||||
foo = fields.ListField(lambda: []) # PIE807
|
||||
bar = fields.ListField(default=lambda: {}) # PIE807
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foo:
|
||||
foo: List[str] = field(default_factory=list)
|
||||
bar: Dict[str, int] = field(default_factory=dict)
|
||||
|
||||
|
||||
class FooTable(BaseTable):
|
||||
bar = fields.ListField(list)
|
||||
foo = fields.ListField(list)
|
||||
bar = fields.ListField(dict)
|
||||
|
||||
|
||||
lambda *args, **kwargs: []
|
||||
lambda *args, **kwargs: {}
|
||||
|
||||
lambda *args: []
|
||||
lambda *args: {}
|
||||
|
||||
lambda **kwargs: []
|
||||
lambda **kwargs: {}
|
||||
|
||||
lambda: {**unwrap}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import abc
|
||||
import builtins
|
||||
import collections.abc
|
||||
import enum
|
||||
import typing
|
||||
from abc import abstractmethod
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
|
||||
from enum import EnumMeta
|
||||
from typing import Any, overload
|
||||
|
||||
import typing_extensions
|
||||
@@ -199,6 +201,31 @@ class AsyncIteratorReturningAsyncIterable:
|
||||
... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
|
||||
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed(type):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed2(EnumMeta):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed2) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed3(enum.EnumType):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed3) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
|
||||
|
||||
class Abstract(Iterator[str]):
|
||||
@abstractmethod
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import abc
|
||||
import builtins
|
||||
import collections.abc
|
||||
import enum
|
||||
import typing
|
||||
from abc import abstractmethod
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
|
||||
from enum import EnumMeta
|
||||
from typing import Any, overload
|
||||
|
||||
import typing_extensions
|
||||
@@ -152,6 +154,30 @@ class AsyncIteratorReturningAsyncIterable:
|
||||
str
|
||||
]: ... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed(type):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed) -> MetaclassInWhichSelfCannotBeUsed: ...
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed2(EnumMeta):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed2) -> MetaclassInWhichSelfCannotBeUsed2: ...
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed3(enum.EnumType):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed3) -> MetaclassInWhichSelfCannotBeUsed3: ...
|
||||
|
||||
class MetaclassInWhichSelfCannotBeUsed4(ABCMeta):
|
||||
def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ...
|
||||
|
||||
class Abstract(Iterator[str]):
|
||||
@abstractmethod
|
||||
def __iter__(self) -> Iterator[str]: ...
|
||||
|
||||
45
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped_unnecessary.py
vendored
Normal file
45
crates/ruff_linter/resources/test/fixtures/flake8_quotes/doubles_escaped_unnecessary.py
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
this_should_raise_Q004 = 'This is a \"string\"'
|
||||
this_should_raise_Q004 = 'This is \\ a \\\"string\"'
|
||||
this_is_fine = '"This" is a \"string\"'
|
||||
this_is_fine = "This is a 'string'"
|
||||
this_is_fine = "\"This\" is a 'string'"
|
||||
this_is_fine = r'This is a \"string\"'
|
||||
this_is_fine = R'This is a \"string\"'
|
||||
this_should_raise_Q004 = (
|
||||
'This is a'
|
||||
'\"string\"'
|
||||
)
|
||||
|
||||
# Same as above, but with f-strings
|
||||
f'This is a \"string\"' # Q004
|
||||
f'This is \\ a \\\"string\"' # Q004
|
||||
f'"This" is a \"string\"'
|
||||
f"This is a 'string'"
|
||||
f"\"This\" is a 'string'"
|
||||
fr'This is a \"string\"'
|
||||
fR'This is a \"string\"'
|
||||
this_should_raise_Q004 = (
|
||||
f'This is a'
|
||||
f'\"string\"' # Q004
|
||||
)
|
||||
|
||||
# Nested f-strings (Python 3.12+)
|
||||
#
|
||||
# The first one is interesting because the fix for it is valid pre 3.12:
|
||||
#
|
||||
# f"'foo' {'nested'}"
|
||||
#
|
||||
# but as the actual string itself is invalid pre 3.12, we don't catch it.
|
||||
f'\"foo\" {'nested'}' # Q004
|
||||
f'\"foo\" {f'nested'}' # Q004
|
||||
f'\"foo\" {f'\"nested\"'} \"\"' # Q004
|
||||
|
||||
f'normal {f'nested'} normal'
|
||||
f'\"normal\" {f'nested'} normal' # Q004
|
||||
f'\"normal\" {f'nested'} "double quotes"'
|
||||
f'\"normal\" {f'\"nested\" {'other'} normal'} "double quotes"' # Q004
|
||||
f'\"normal\" {f'\"nested\" {'other'} "double quotes"'} normal' # Q004
|
||||
|
||||
# Make sure we do not unescape quotes
|
||||
this_is_fine = 'This is an \\"escaped\\" quote'
|
||||
this_should_raise_Q004 = 'This is an \\\"escaped\\\" quote with an extra backslash'
|
||||
43
crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped_unnecessary.py
vendored
Normal file
43
crates/ruff_linter/resources/test/fixtures/flake8_quotes/singles_escaped_unnecessary.py
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
this_should_raise_Q004 = "This is a \'string\'"
|
||||
this_should_raise_Q004 = "'This' is a \'string\'"
|
||||
this_is_fine = 'This is a "string"'
|
||||
this_is_fine = '\'This\' is a "string"'
|
||||
this_is_fine = r"This is a \'string\'"
|
||||
this_is_fine = R"This is a \'string\'"
|
||||
this_should_raise_Q004 = (
|
||||
"This is a"
|
||||
"\'string\'"
|
||||
)
|
||||
|
||||
# Same as above, but with f-strings
|
||||
f"This is a \'string\'" # Q004
|
||||
f"'This' is a \'string\'" # Q004
|
||||
f'This is a "string"'
|
||||
f'\'This\' is a "string"'
|
||||
fr"This is a \'string\'"
|
||||
fR"This is a \'string\'"
|
||||
this_should_raise_Q004 = (
|
||||
f"This is a"
|
||||
f"\'string\'" # Q004
|
||||
)
|
||||
|
||||
# Nested f-strings (Python 3.12+)
|
||||
#
|
||||
# The first one is interesting because the fix for it is valid pre 3.12:
|
||||
#
|
||||
# f'"foo" {"nested"}'
|
||||
#
|
||||
# but as the actual string itself is invalid pre 3.12, we don't catch it.
|
||||
f"\'foo\' {"foo"}" # Q004
|
||||
f"\'foo\' {f"foo"}" # Q004
|
||||
f"\'foo\' {f"\'foo\'"} \'\'" # Q004
|
||||
|
||||
f"normal {f"nested"} normal"
|
||||
f"\'normal\' {f"nested"} normal" # Q004
|
||||
f"\'normal\' {f"nested"} 'single quotes'"
|
||||
f"\'normal\' {f"\'nested\' {"other"} normal"} 'single quotes'" # Q004
|
||||
f"\'normal\' {f"\'nested\' {"other"} 'single quotes'"} normal" # Q004
|
||||
|
||||
# Make sure we do not unescape quotes
|
||||
this_is_fine = "This is an \\'escaped\\' quote"
|
||||
this_should_raise_Q004 = "This is an \\\'escaped\\\' quote with an extra backslash"
|
||||
13
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO109.py
vendored
Normal file
13
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO109.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import trio
|
||||
|
||||
|
||||
async def func():
|
||||
...
|
||||
|
||||
|
||||
async def func(timeout):
|
||||
...
|
||||
|
||||
|
||||
async def func(timeout=10):
|
||||
...
|
||||
16
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO110.py
vendored
Normal file
16
crates/ruff_linter/resources/test/fixtures/flake8_trio/TRIO110.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import trio
|
||||
|
||||
|
||||
async def func():
|
||||
while True:
|
||||
await trio.sleep(10)
|
||||
|
||||
|
||||
async def func():
|
||||
while True:
|
||||
await trio.sleep_until(10)
|
||||
|
||||
|
||||
async def func():
|
||||
while True:
|
||||
trio.sleep(10)
|
||||
@@ -172,3 +172,14 @@ def f():
|
||||
from module import Member
|
||||
|
||||
x: Member = 1
|
||||
|
||||
|
||||
def f():
|
||||
from typing_extensions import TYPE_CHECKING
|
||||
|
||||
from pandas import y
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_type = x
|
||||
elif True:
|
||||
_type = y
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_16.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_16.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing_extensions import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
def example() -> DataFrame:
|
||||
pass
|
||||
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_17.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_17.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing_extensions import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
def example() -> DataFrame:
|
||||
x = DataFrame()
|
||||
@@ -37,3 +37,9 @@ if False:
|
||||
|
||||
if 0:
|
||||
x: List
|
||||
|
||||
|
||||
from typing_extensions import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass # TCH005
|
||||
|
||||
0
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/__init__.py
vendored
Normal file
0
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/__init__.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/direct.py
vendored
Normal file
11
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/direct.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
class MyBaseClass:
|
||||
pass
|
||||
|
||||
|
||||
class Foo(MyBaseClass):
|
||||
foo: Sequence
|
||||
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/import.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/import.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from module.direct import MyBaseClass
|
||||
|
||||
|
||||
class Foo(MyBaseClass):
|
||||
foo: Sequence
|
||||
7
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/undefined.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/undefined.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
class Foo(MyBaseClass):
|
||||
foo: Sequence
|
||||
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence # TCH003
|
||||
|
||||
|
||||
class MyBaseClass:
|
||||
pass
|
||||
|
||||
|
||||
class Foo(MyBaseClass):
|
||||
foo: Sequence
|
||||
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/typing_modules_1.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/typing_modules_1.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
def func():
|
||||
from pandas import DataFrame
|
||||
|
||||
df: DataFrame
|
||||
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/typing_modules_2.py
vendored
Normal file
9
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/typing_modules_2.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing_extensions
|
||||
|
||||
|
||||
def func():
|
||||
from pandas import DataFrame
|
||||
|
||||
df: DataFrame
|
||||
5
crates/ruff_linter/resources/test/fixtures/isort/force_sort_within_sections_with_as_names.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/isort/force_sort_within_sections_with_as_names.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import encodings
|
||||
from datetime import timezone as tz
|
||||
from datetime import timedelta
|
||||
import datetime as dt
|
||||
import datetime
|
||||
8
crates/ruff_linter/resources/test/fixtures/isort/no_sections.py
vendored
Normal file
8
crates/ruff_linter/resources/test/fixtures/isort/no_sections.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import django.settings
|
||||
import os
|
||||
import pytz
|
||||
import sys
|
||||
from . import local
|
||||
from library import foo
|
||||
@@ -63,3 +63,33 @@ class PosOnlyClass:
|
||||
|
||||
def bad_method_pos_only(this, blah, /, self, something: str):
|
||||
pass
|
||||
|
||||
|
||||
class ModelClass:
|
||||
@hybrid_property
|
||||
def bad(cls):
|
||||
pass
|
||||
|
||||
@bad.expression
|
||||
def bad(self):
|
||||
pass
|
||||
|
||||
@bad.wtf
|
||||
def bad(cls):
|
||||
pass
|
||||
|
||||
@hybrid_property
|
||||
def good(self):
|
||||
pass
|
||||
|
||||
@good.expression
|
||||
def good(cls):
|
||||
pass
|
||||
|
||||
@good.wtf
|
||||
def good(self):
|
||||
pass
|
||||
|
||||
@foobar.thisisstatic
|
||||
def badstatic(foo):
|
||||
pass
|
||||
|
||||
@@ -89,3 +89,49 @@ x = [ #
|
||||
f"{ {'a': 1} }"
|
||||
f"{[ { {'a': 1} } ]}"
|
||||
f"normal { {f"{ { [1, 2] } }" } } normal"
|
||||
|
||||
#: Okay
|
||||
ham[lower + offset : upper + offset]
|
||||
|
||||
#: Okay
|
||||
ham[(lower + offset) : upper + offset]
|
||||
|
||||
#: E203:1:19
|
||||
ham{lower + offset : upper + offset}
|
||||
|
||||
#: E203:1:19
|
||||
ham[lower + offset : upper + offset]
|
||||
|
||||
#: Okay
|
||||
release_lines = history_file_lines[history_file_lines.index('## Unreleased') + 1: -1]
|
||||
|
||||
#: Okay
|
||||
release_lines = history_file_lines[history_file_lines.index('## Unreleased') + 1 : -1]
|
||||
|
||||
#: Okay
|
||||
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
|
||||
ham[lower:upper], ham[lower:upper:], ham[lower::step]
|
||||
ham[lower+offset : upper+offset]
|
||||
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
|
||||
ham[lower + offset : upper + offset]
|
||||
|
||||
#: E201:1:5
|
||||
ham[ : upper]
|
||||
|
||||
#: Okay
|
||||
ham[lower + offset :: upper + offset]
|
||||
|
||||
#: Okay
|
||||
ham[(lower + offset) :: upper + offset]
|
||||
|
||||
#: Okay
|
||||
ham[lower + offset::upper + offset]
|
||||
|
||||
#: E203:1:21
|
||||
ham[lower + offset : : upper + offset]
|
||||
|
||||
#: E203:1:20
|
||||
ham[lower + offset: :upper + offset]
|
||||
|
||||
#: E203:1:20
|
||||
ham[{lower + offset : upper + offset} : upper + offset]
|
||||
|
||||
@@ -16,6 +16,48 @@ if False == None: # E711, E712 (fix)
|
||||
if None == False: # E711, E712 (fix)
|
||||
pass
|
||||
|
||||
named_var = []
|
||||
if [] is []: # F632 (fix)
|
||||
pass
|
||||
if named_var is []: # F632 (fix)
|
||||
pass
|
||||
if [] is named_var: # F632 (fix)
|
||||
pass
|
||||
if named_var is [1]: # F632 (fix)
|
||||
pass
|
||||
if [1] is named_var: # F632 (fix)
|
||||
pass
|
||||
if named_var is [i for i in [1]]: # F632 (fix)
|
||||
pass
|
||||
|
||||
named_var = {}
|
||||
if {} is {}: # F632 (fix)
|
||||
pass
|
||||
if named_var is {}: # F632 (fix)
|
||||
pass
|
||||
if {} is named_var: # F632 (fix)
|
||||
pass
|
||||
if named_var is {1}: # F632 (fix)
|
||||
pass
|
||||
if {1} is named_var: # F632 (fix)
|
||||
pass
|
||||
if named_var is {i for i in [1]}: # F632 (fix)
|
||||
pass
|
||||
|
||||
named_var = {1: 1}
|
||||
if {1: 1} is {1: 1}: # F632 (fix)
|
||||
pass
|
||||
if named_var is {1: 1}: # F632 (fix)
|
||||
pass
|
||||
if {1: 1} is named_var: # F632 (fix)
|
||||
pass
|
||||
if named_var is {1: 1}: # F632 (fix)
|
||||
pass
|
||||
if {1: 1} is named_var: # F632 (fix)
|
||||
pass
|
||||
if named_var is {i: 1 for i in [1]}: # F632 (fix)
|
||||
pass
|
||||
|
||||
###
|
||||
# Non-errors
|
||||
###
|
||||
@@ -33,3 +75,45 @@ if False is None:
|
||||
pass
|
||||
if None is False:
|
||||
pass
|
||||
|
||||
named_var = []
|
||||
if [] == []:
|
||||
pass
|
||||
if named_var == []:
|
||||
pass
|
||||
if [] == named_var:
|
||||
pass
|
||||
if named_var == [1]:
|
||||
pass
|
||||
if [1] == named_var:
|
||||
pass
|
||||
if named_var == [i for i in [1]]:
|
||||
pass
|
||||
|
||||
named_var = {}
|
||||
if {} == {}:
|
||||
pass
|
||||
if named_var == {}:
|
||||
pass
|
||||
if {} == named_var:
|
||||
pass
|
||||
if named_var == {1}:
|
||||
pass
|
||||
if {1} == named_var:
|
||||
pass
|
||||
if named_var == {i for i in [1]}:
|
||||
pass
|
||||
|
||||
named_var = {1: 1}
|
||||
if {1: 1} == {1: 1}:
|
||||
pass
|
||||
if named_var == {1: 1}:
|
||||
pass
|
||||
if {1: 1} == named_var:
|
||||
pass
|
||||
if named_var == {1: 1}:
|
||||
pass
|
||||
if {1: 1} == named_var:
|
||||
pass
|
||||
if named_var == {i: 1 for i in [1]}:
|
||||
pass
|
||||
|
||||
4
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_21.py
vendored
Normal file
4
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_21.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
"""Test for IPython-only builtins."""
|
||||
|
||||
x = 1
|
||||
display(x)
|
||||
74
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_22.ipynb
vendored
Normal file
74
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_22.ipynb
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "af8fee97-f9aa-47c2-b34c-b109a5f083d6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "acd5eb1e-6991-42b8-806f-20d17d7e571f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"display(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f8f3f599-030e-48b3-bcd3-31d97f725c68",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
79
crates/ruff_linter/resources/test/fixtures/pylint/redefined_argument_from_local.py
vendored
Normal file
79
crates/ruff_linter/resources/test/fixtures/pylint/redefined_argument_from_local.py
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
# No Errors
|
||||
def func(a):
|
||||
for b in range(1):
|
||||
...
|
||||
|
||||
|
||||
def func(a):
|
||||
try:
|
||||
...
|
||||
except ValueError:
|
||||
...
|
||||
except KeyError:
|
||||
...
|
||||
|
||||
|
||||
if True:
|
||||
def func(a):
|
||||
...
|
||||
else:
|
||||
for a in range(1):
|
||||
print(a)
|
||||
|
||||
|
||||
# Errors
|
||||
def func(a):
|
||||
for a in range(1):
|
||||
...
|
||||
|
||||
|
||||
def func(i):
|
||||
for i in range(10):
|
||||
print(i)
|
||||
|
||||
|
||||
def func(e):
|
||||
try:
|
||||
...
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def func(f):
|
||||
with open('', ) as f:
|
||||
print(f)
|
||||
|
||||
|
||||
def func(a, b):
|
||||
with context() as (a, b, c):
|
||||
print(a, b, c)
|
||||
|
||||
|
||||
def func(a, b):
|
||||
with context() as [a, b, c]:
|
||||
print(a, b, c)
|
||||
|
||||
|
||||
def func(a):
|
||||
with open('foo.py', ) as f, open('bar.py') as a:
|
||||
...
|
||||
|
||||
|
||||
def func(a):
|
||||
def bar(b):
|
||||
for a in range(1):
|
||||
print(a)
|
||||
|
||||
|
||||
def func(a):
|
||||
def bar(b):
|
||||
for b in range(1):
|
||||
print(b)
|
||||
|
||||
|
||||
def func(a=1):
|
||||
def bar(b=2):
|
||||
for a in range(1):
|
||||
print(a)
|
||||
for b in range(1):
|
||||
print(b)
|
||||
@@ -114,3 +114,8 @@ class ServiceRefOrValue:
|
||||
class Collection(Protocol[*_B0]):
|
||||
def __iter__(self) -> Iterator[Union[*_B0]]:
|
||||
...
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/8609
|
||||
def f(x: Union[int, str, bytes]) -> None:
|
||||
...
|
||||
|
||||
@@ -25,3 +25,6 @@ u = u
|
||||
|
||||
def hello():
|
||||
return"Hello"
|
||||
|
||||
f"foo"u"bar"
|
||||
f"foo" u"bar"
|
||||
|
||||
@@ -207,3 +207,22 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
# The fixed string will exceed the line length, but it's still smaller than the
|
||||
# existing line length, so it's fine.
|
||||
"<Customer: {}, {}, {}, {}, {}>".format(self.internal_ids, self.external_ids, self.properties, self.tags, self.others)
|
||||
|
||||
# When fixing, trim the trailing empty string.
|
||||
raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
"".format(new_dict, d))
|
||||
|
||||
# When fixing, trim the trailing empty string.
|
||||
raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
.format(new_dict, d))
|
||||
|
||||
raise ValueError(
|
||||
"Conflicting configuration dicts: {!r} {!r}"
|
||||
"".format(new_dict, d)
|
||||
)
|
||||
|
||||
raise ValueError(
|
||||
"Conflicting configuration dicts: {!r} {!r}"
|
||||
"".format(new_dict, d)
|
||||
|
||||
)
|
||||
|
||||
@@ -123,6 +123,15 @@ def yes_six(x: list):
|
||||
x.append(2)
|
||||
|
||||
|
||||
if True:
|
||||
# FURB113
|
||||
nums.append(1)
|
||||
# comment
|
||||
nums.append(2)
|
||||
# comment
|
||||
nums.append(3)
|
||||
|
||||
|
||||
# Non-errors.
|
||||
|
||||
nums.append(1)
|
||||
|
||||
25
crates/ruff_linter/resources/test/fixtures/refurb/FURB136.py
vendored
Normal file
25
crates/ruff_linter/resources/test/fixtures/refurb/FURB136.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
x = 1
|
||||
y = 2
|
||||
|
||||
x if x > y else y # FURB136
|
||||
|
||||
x if x >= y else y # FURB136
|
||||
|
||||
x if x < y else y # FURB136
|
||||
|
||||
x if x <= y else y # FURB136
|
||||
|
||||
y if x > y else x # FURB136
|
||||
|
||||
y if x >= y else x # FURB136
|
||||
|
||||
y if x < y else x # FURB136
|
||||
|
||||
y if x <= y else x # FURB136
|
||||
|
||||
x + y if x > y else y # OK
|
||||
|
||||
x if (
|
||||
x
|
||||
> y
|
||||
) else y # FURB136
|
||||
@@ -18,8 +18,8 @@ pub(crate) fn deferred_lambdas(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryLambda) {
|
||||
pylint::rules::unnecessary_lambda(checker, lambda);
|
||||
}
|
||||
if checker.enabled(Rule::ReimplementedListBuiltin) {
|
||||
flake8_pie::rules::reimplemented_list_builtin(checker, lambda);
|
||||
if checker.enabled(Rule::ReimplementedContainerBuiltin) {
|
||||
flake8_pie::rules::reimplemented_container_builtin(checker, lambda);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
if !checker.any_enabled(&[
|
||||
Rule::GlobalVariableNotAssigned,
|
||||
Rule::ImportShadowedByLoopVar,
|
||||
Rule::RedefinedArgumentFromLocal,
|
||||
Rule::RedefinedWhileUnused,
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
@@ -89,6 +90,32 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::RedefinedArgumentFromLocal) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
|
||||
let binding = &checker.semantic.bindings[shadow.binding_id()];
|
||||
if !matches!(
|
||||
binding.kind,
|
||||
BindingKind::LoopVar
|
||||
| BindingKind::BoundException
|
||||
| BindingKind::WithItemVar
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let shadowed = &checker.semantic.bindings[shadow.shadowed_id()];
|
||||
if !shadowed.kind.is_argument() {
|
||||
continue;
|
||||
}
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pylint::rules::RedefinedArgumentFromLocal {
|
||||
name: name.to_string(),
|
||||
},
|
||||
binding.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::ImportShadowedByLoopVar) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
for shadow in checker.semantic.shadowed_bindings(scope_id, binding_id) {
|
||||
|
||||
@@ -571,6 +571,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::Jinja2AutoescapeFalse) {
|
||||
flake8_bandit::rules::jinja2_autoescape_false(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::MakoTemplates) {
|
||||
flake8_bandit::rules::mako_templates(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedPasswordFuncArg) {
|
||||
flake8_bandit::rules::hardcoded_password_func_arg(checker, keywords);
|
||||
}
|
||||
@@ -933,13 +936,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_trio::rules::zero_sleep_call(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(
|
||||
dict @ ast::ExprDict {
|
||||
keys,
|
||||
values,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
Expr::Dict(dict) => {
|
||||
if checker.any_enabled(&[
|
||||
Rule::MultiValueRepeatedKeyLiteral,
|
||||
Rule::MultiValueRepeatedKeyVariable,
|
||||
@@ -947,7 +944,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyflakes::rules::repeated_keys(checker, dict);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessarySpread) {
|
||||
flake8_pie::rules::unnecessary_spread(checker, keys, values);
|
||||
flake8_pie::rules::unnecessary_spread(checker, dict);
|
||||
}
|
||||
}
|
||||
Expr::Set(ast::ExprSet { elts, range: _ }) => {
|
||||
@@ -1269,21 +1266,20 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::HardcodedTempFile) {
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, string);
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, string);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::IfExp(ast::ExprIfExp {
|
||||
test,
|
||||
body,
|
||||
orelse,
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::IfExp(
|
||||
if_exp @ ast::ExprIfExp {
|
||||
test,
|
||||
body,
|
||||
orelse,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.enabled(Rule::IfElseBlockInsteadOfDictGet) {
|
||||
flake8_simplify::rules::if_exp_instead_of_dict_get(
|
||||
checker, expr, test, body, orelse,
|
||||
@@ -1298,6 +1294,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::IfExprWithTwistedArms) {
|
||||
flake8_simplify::rules::twisted_arms_in_ifexpr(checker, expr, test, body, orelse);
|
||||
}
|
||||
if checker.enabled(Rule::IfExprMinMax) {
|
||||
refurb::rules::if_expr_min_max(checker, if_exp);
|
||||
}
|
||||
}
|
||||
Expr::ListComp(
|
||||
comp @ ast::ExprListComp {
|
||||
|
||||
@@ -356,6 +356,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::TrioAsyncFunctionWithTimeout) {
|
||||
flake8_trio::rules::async_function_with_timeout(checker, function_def);
|
||||
}
|
||||
#[cfg(feature = "unreachable-code")]
|
||||
if checker.enabled(Rule::UnreachableCode) {
|
||||
checker
|
||||
@@ -1206,7 +1209,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_trio::rules::timeout_without_await(checker, with_stmt, items);
|
||||
}
|
||||
}
|
||||
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||
Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => {
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Stmt(stmt));
|
||||
}
|
||||
@@ -1216,6 +1219,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::TryExceptInLoop) {
|
||||
perflint::rules::try_except_in_loop(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::TrioUnneededSleep) {
|
||||
flake8_trio::rules::unneeded_sleep(checker, while_stmt);
|
||||
}
|
||||
}
|
||||
Stmt::For(
|
||||
for_stmt @ ast::StmtFor {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::rules::flake8_pie;
|
||||
|
||||
/// Run lint rules over a suite of [`Stmt`] syntax nodes.
|
||||
pub(crate) fn suite(suite: &[Stmt], checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryPass) {
|
||||
flake8_pie::rules::no_unnecessary_pass(checker, suite);
|
||||
if checker.enabled(Rule::UnnecessaryPlaceholder) {
|
||||
flake8_pie::rules::unnecessary_placeholder(checker, suite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ use ruff_python_semantic::{
|
||||
ModuleKind, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, Snapshot,
|
||||
StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use ruff_python_stdlib::builtins::{IPYTHON_BUILTINS, MAGIC_GLOBALS, PYTHON_BUILTINS};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::checkers::ast::deferred::Deferred;
|
||||
@@ -1415,7 +1415,7 @@ impl<'a> Checker<'a> {
|
||||
// subsequent nodes are evaluated in the inner scope.
|
||||
//
|
||||
// For example, given:
|
||||
// ```py
|
||||
// ```python
|
||||
// class A:
|
||||
// T = range(10)
|
||||
//
|
||||
@@ -1423,7 +1423,7 @@ impl<'a> Checker<'a> {
|
||||
// ```
|
||||
//
|
||||
// Conceptually, this is compiled as:
|
||||
// ```py
|
||||
// ```python
|
||||
// class A:
|
||||
// T = range(10)
|
||||
//
|
||||
@@ -1592,9 +1592,16 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn bind_builtins(&mut self) {
|
||||
for builtin in BUILTINS
|
||||
for builtin in PYTHON_BUILTINS
|
||||
.iter()
|
||||
.chain(MAGIC_GLOBALS.iter())
|
||||
.chain(
|
||||
self.source_type
|
||||
.is_ipynb()
|
||||
.then_some(IPYTHON_BUILTINS)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.copied()
|
||||
.chain(self.settings.builtins.iter().map(String::as_str))
|
||||
{
|
||||
@@ -1615,38 +1622,28 @@ impl<'a> Checker<'a> {
|
||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||
let parent = self.semantic.current_statement();
|
||||
|
||||
let mut flags = BindingFlags::empty();
|
||||
if helpers::is_unpacking_assignment(parent, expr) {
|
||||
flags.insert(BindingFlags::UNPACKED_ASSIGNMENT);
|
||||
}
|
||||
|
||||
// Match the left-hand side of an annotated assignment, like `x` in `x: int`.
|
||||
if matches!(
|
||||
parent,
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
|
||||
) && !self.semantic.in_annotation()
|
||||
{
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
BindingKind::Annotation,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
self.add_binding(id, expr.range(), BindingKind::Annotation, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
if parent.is_for_stmt() {
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
BindingKind::LoopVar,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
self.add_binding(id, expr.range(), BindingKind::LoopVar, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
if helpers::is_unpacking_assignment(parent, expr) {
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
BindingKind::UnpackedAssignment,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
if parent.is_with_stmt() {
|
||||
self.add_binding(id, expr.range(), BindingKind::WithItemVar, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1681,7 +1678,6 @@ impl<'a> Checker<'a> {
|
||||
let (all_names, all_flags) =
|
||||
extract_all_names(parent, |name| self.semantic.is_builtin(name));
|
||||
|
||||
let mut flags = BindingFlags::empty();
|
||||
if all_flags.intersects(DunderAllFlags::INVALID_OBJECT) {
|
||||
flags |= BindingFlags::INVALID_ALL_OBJECT;
|
||||
}
|
||||
@@ -1705,21 +1701,11 @@ impl<'a> Checker<'a> {
|
||||
.current_expressions()
|
||||
.any(Expr::is_named_expr_expr)
|
||||
{
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
BindingKind::NamedExprAssignment,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
self.add_binding(id, expr.range(), BindingKind::NamedExprAssignment, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
BindingKind::Assignment,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
self.add_binding(id, expr.range(), BindingKind::Assignment, flags);
|
||||
}
|
||||
|
||||
fn handle_node_delete(&mut self, expr: &'a Expr) {
|
||||
|
||||
@@ -91,6 +91,10 @@ pub(crate) fn check_tokens(
|
||||
pycodestyle::rules::tab_indentation(&mut diagnostics, tokens, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(&mut diagnostics, tokens);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::InvalidCharacterBackspace,
|
||||
Rule::InvalidCharacterSub,
|
||||
@@ -115,6 +119,10 @@ pub(crate) fn check_tokens(
|
||||
flake8_quotes::rules::avoidable_escaped_quote(&mut diagnostics, tokens, locator, settings);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::UnnecessaryEscapedQuote) {
|
||||
flake8_quotes::rules::unnecessary_escaped_quote(&mut diagnostics, tokens, locator);
|
||||
}
|
||||
|
||||
if settings.rules.any_enabled(&[
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
@@ -141,7 +149,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
]) {
|
||||
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator);
|
||||
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, indexer);
|
||||
}
|
||||
|
||||
if settings.rules.enabled(Rule::ExtraneousParentheses) {
|
||||
|
||||
@@ -252,6 +252,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0915") => (RuleGroup::Stable, rules::pylint::rules::TooManyStatements),
|
||||
(Pylint, "R0916") => (RuleGroup::Preview, rules::pylint::rules::TooManyBooleanExpressions),
|
||||
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
|
||||
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1706") => (RuleGroup::Preview, rules::pylint::rules::AndOrTernary),
|
||||
@@ -293,6 +294,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, "109") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout),
|
||||
(Flake8Trio, "110") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioUnneededSleep),
|
||||
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
|
||||
|
||||
// flake8-builtins
|
||||
@@ -400,6 +403,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Quotes, "001") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesMultilineString),
|
||||
(Flake8Quotes, "002") => (RuleGroup::Stable, rules::flake8_quotes::rules::BadQuotesDocstring),
|
||||
(Flake8Quotes, "003") => (RuleGroup::Stable, rules::flake8_quotes::rules::AvoidableEscapedQuote),
|
||||
(Flake8Quotes, "004") => (RuleGroup::Preview, rules::flake8_quotes::rules::UnnecessaryEscapedQuote),
|
||||
|
||||
// flake8-annotations
|
||||
(Flake8Annotations, "001") => (RuleGroup::Stable, rules::flake8_annotations::rules::MissingTypeFunctionArgument),
|
||||
@@ -629,6 +633,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bandit, "609") => (RuleGroup::Stable, rules::flake8_bandit::rules::UnixCommandWildcardInjection),
|
||||
(Flake8Bandit, "612") => (RuleGroup::Stable, rules::flake8_bandit::rules::LoggingConfigInsecureListen),
|
||||
(Flake8Bandit, "701") => (RuleGroup::Stable, rules::flake8_bandit::rules::Jinja2AutoescapeFalse),
|
||||
(Flake8Bandit, "702") => (RuleGroup::Preview, rules::flake8_bandit::rules::MakoTemplates),
|
||||
|
||||
// flake8-boolean-trap
|
||||
(Flake8BooleanTrap, "001") => (RuleGroup::Stable, rules::flake8_boolean_trap::rules::BooleanTypeHintPositionalArgument),
|
||||
@@ -764,12 +769,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8PytestStyle, "027") => (RuleGroup::Stable, rules::flake8_pytest_style::rules::PytestUnittestRaisesAssertion),
|
||||
|
||||
// flake8-pie
|
||||
(Flake8Pie, "790") => (RuleGroup::Stable, rules::flake8_pie::rules::UnnecessaryPass),
|
||||
(Flake8Pie, "790") => (RuleGroup::Stable, rules::flake8_pie::rules::UnnecessaryPlaceholder),
|
||||
(Flake8Pie, "794") => (RuleGroup::Stable, rules::flake8_pie::rules::DuplicateClassFieldDefinition),
|
||||
(Flake8Pie, "796") => (RuleGroup::Stable, rules::flake8_pie::rules::NonUniqueEnums),
|
||||
(Flake8Pie, "800") => (RuleGroup::Stable, rules::flake8_pie::rules::UnnecessarySpread),
|
||||
(Flake8Pie, "804") => (RuleGroup::Stable, rules::flake8_pie::rules::UnnecessaryDictKwargs),
|
||||
(Flake8Pie, "807") => (RuleGroup::Stable, rules::flake8_pie::rules::ReimplementedListBuiltin),
|
||||
(Flake8Pie, "807") => (RuleGroup::Stable, rules::flake8_pie::rules::ReimplementedContainerBuiltin),
|
||||
(Flake8Pie, "808") => (RuleGroup::Stable, rules::flake8_pie::rules::UnnecessaryRangeStart),
|
||||
(Flake8Pie, "810") => (RuleGroup::Stable, rules::flake8_pie::rules::MultipleStartsEndsWith),
|
||||
|
||||
@@ -942,6 +947,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
|
||||
#[allow(deprecated)]
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "136") => (RuleGroup::Preview, rules::refurb::rules::IfExprMinMax),
|
||||
(Refurb, "140") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedStarmap),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
(Refurb, "148") => (RuleGroup::Preview, rules::refurb::rules::UnnecessaryEnumerate),
|
||||
|
||||
@@ -189,7 +189,7 @@ impl<'a> Insertion<'a> {
|
||||
Tok::NonLogicalNewline => {}
|
||||
Tok::Indent => {
|
||||
// This is like:
|
||||
// ```py
|
||||
// ```python
|
||||
// if True:
|
||||
// pass
|
||||
// ```
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::error::Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use libcst_native::{ImportAlias, Name, NameOrAttribute};
|
||||
use ruff_python_ast::{self as ast, PySourceType, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, PySourceType, Stmt};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -26,7 +26,7 @@ mod insertion;
|
||||
|
||||
pub(crate) struct Importer<'a> {
|
||||
/// The Python AST to which we are adding imports.
|
||||
python_ast: &'a Suite,
|
||||
python_ast: &'a [Stmt],
|
||||
/// The [`Locator`] for the Python AST.
|
||||
locator: &'a Locator<'a>,
|
||||
/// The [`Stylist`] for the Python AST.
|
||||
@@ -39,7 +39,7 @@ pub(crate) struct Importer<'a> {
|
||||
|
||||
impl<'a> Importer<'a> {
|
||||
pub(crate) fn new(
|
||||
python_ast: &'a Suite,
|
||||
python_ast: &'a [Stmt],
|
||||
locator: &'a Locator<'a>,
|
||||
stylist: &'a Stylist<'a>,
|
||||
) -> Self {
|
||||
@@ -132,11 +132,7 @@ impl<'a> Importer<'a> {
|
||||
)?;
|
||||
|
||||
// Import the `TYPE_CHECKING` symbol from the typing module.
|
||||
let (type_checking_edit, type_checking) = self.get_or_import_symbol(
|
||||
&ImportRequest::import_from("typing", "TYPE_CHECKING"),
|
||||
at,
|
||||
semantic,
|
||||
)?;
|
||||
let (type_checking_edit, type_checking) = self.get_or_import_type_checking(at, semantic)?;
|
||||
|
||||
// Add the import to a `TYPE_CHECKING` block.
|
||||
let add_import_edit = if let Some(block) = self.preceding_type_checking_block(at) {
|
||||
@@ -161,6 +157,30 @@ impl<'a> Importer<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] to reference `typing.TYPE_CHECKING`. Returns the [`Edit`] necessary to
|
||||
/// make the symbol available in the current scope along with the bound name of the symbol.
|
||||
fn get_or_import_type_checking(
|
||||
&self,
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
) -> Result<(Edit, String), ResolutionError> {
|
||||
for module in semantic.typing_modules() {
|
||||
if let Some((edit, name)) = self.get_symbol(
|
||||
&ImportRequest::import_from(module, "TYPE_CHECKING"),
|
||||
at,
|
||||
semantic,
|
||||
)? {
|
||||
return Ok((edit, name));
|
||||
}
|
||||
}
|
||||
|
||||
self.import_symbol(
|
||||
&ImportRequest::import_from("typing", "TYPE_CHECKING"),
|
||||
at,
|
||||
semantic,
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] to reference the given symbol. Returns the [`Edit`] necessary to make
|
||||
/// the symbol available in the current scope along with the bound name of the symbol.
|
||||
///
|
||||
@@ -209,7 +229,7 @@ impl<'a> Importer<'a> {
|
||||
// We also add a no-op edit to force conflicts with any other fixes that might try to
|
||||
// remove the import. Consider:
|
||||
//
|
||||
// ```py
|
||||
// ```python
|
||||
// import sys
|
||||
//
|
||||
// quit()
|
||||
|
||||
@@ -8,7 +8,7 @@ use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Applicability, Diagnostic};
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
@@ -268,24 +268,13 @@ pub fn check_path(
|
||||
}
|
||||
|
||||
// Update fix applicability to account for overrides
|
||||
if !settings.extend_safe_fixes.is_empty() || !settings.extend_unsafe_fixes.is_empty() {
|
||||
if !settings.fix_safety.is_empty() {
|
||||
for diagnostic in &mut diagnostics {
|
||||
if let Some(fix) = diagnostic.fix.take() {
|
||||
// Enforce demotions over promotions so if someone puts a rule in both we are conservative
|
||||
if fix.applicability().is_safe()
|
||||
&& settings
|
||||
.extend_unsafe_fixes
|
||||
.contains(diagnostic.kind.rule())
|
||||
{
|
||||
diagnostic.set_fix(fix.with_applicability(Applicability::Unsafe));
|
||||
} else if fix.applicability().is_unsafe()
|
||||
&& settings.extend_safe_fixes.contains(diagnostic.kind.rule())
|
||||
{
|
||||
diagnostic.set_fix(fix.with_applicability(Applicability::Safe));
|
||||
} else {
|
||||
// Retain the existing fix (will be dropped from `.take()` otherwise)
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
let fixed_applicability = settings
|
||||
.fix_safety
|
||||
.resolve_applicability(diagnostic.kind.rule(), fix.applicability());
|
||||
diagnostic.set_fix(fix.with_applicability(fixed_applicability));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,7 +639,7 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::test::{test_contents, test_notebook_path, TestedNotebook};
|
||||
use crate::test::{assert_notebook_path, test_contents, TestedNotebook};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
|
||||
@@ -666,7 +655,7 @@ mod tests {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UnsortedImports),
|
||||
@@ -683,7 +672,7 @@ mod tests {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
||||
@@ -700,7 +689,7 @@ mod tests {
|
||||
messages,
|
||||
source_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedVariable),
|
||||
@@ -717,7 +706,7 @@ mod tests {
|
||||
let TestedNotebook {
|
||||
linted_notebook: fixed_notebook,
|
||||
..
|
||||
} = test_notebook_path(
|
||||
} = assert_notebook_path(
|
||||
actual_path,
|
||||
&expected_path,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
||||
|
||||
@@ -54,9 +54,9 @@ impl Display for Diff<'_> {
|
||||
|
||||
let message = match self.fix.applicability() {
|
||||
// TODO(zanieb): Adjust this messaging once it's user-facing
|
||||
Applicability::Safe => "Fix",
|
||||
Applicability::Unsafe => "Suggested fix",
|
||||
Applicability::Display => "Possible fix",
|
||||
Applicability::Safe => "Safe fix",
|
||||
Applicability::Unsafe => "Unsafe fix",
|
||||
Applicability::DisplayOnly => "Display-only fix",
|
||||
};
|
||||
writeln!(f, "ℹ {}", message.blue())?;
|
||||
|
||||
|
||||
@@ -297,6 +297,7 @@ impl Rule {
|
||||
| Rule::TabIndentation
|
||||
| Rule::TrailingCommaOnBareTuple
|
||||
| Rule::TypeCommentInStub
|
||||
| Rule::UnicodeKindPrefix
|
||||
| Rule::UselessSemicolon
|
||||
| Rule::UTF8EncodingDeclaration => LintSource::Tokens,
|
||||
Rule::IOError => LintSource::Io,
|
||||
|
||||
@@ -245,10 +245,10 @@ impl Renamer {
|
||||
| BindingKind::Argument
|
||||
| BindingKind::TypeParam
|
||||
| BindingKind::NamedExprAssignment
|
||||
| BindingKind::UnpackedAssignment
|
||||
| BindingKind::Assignment
|
||||
| BindingKind::BoundException
|
||||
| BindingKind::LoopVar
|
||||
| BindingKind::WithItemVar
|
||||
| BindingKind::Global
|
||||
| BindingKind::Nonlocal(_)
|
||||
| BindingKind::ClassDefinition(_)
|
||||
|
||||
@@ -66,7 +66,7 @@ pub(crate) fn commented_out_code(
|
||||
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
let mut diagnostic = Diagnostic::new(CommentedOutCode, *range);
|
||||
|
||||
diagnostic.set_fix(Fix::display_edit(Edit::range_deletion(
|
||||
diagnostic.set_fix(Fix::display_only_edit(Edit::range_deletion(
|
||||
locator.full_lines_range(*range),
|
||||
)));
|
||||
diagnostics.push(diagnostic);
|
||||
|
||||
@@ -10,7 +10,7 @@ ERA001.py:1:1: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
1 |-#import os
|
||||
2 1 | # from foo import junk
|
||||
3 2 | #a = 3
|
||||
@@ -26,7 +26,7 @@ ERA001.py:2:1: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
1 1 | #import os
|
||||
2 |-# from foo import junk
|
||||
3 2 | #a = 3
|
||||
@@ -44,7 +44,7 @@ ERA001.py:3:1: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
1 1 | #import os
|
||||
2 2 | # from foo import junk
|
||||
3 |-#a = 3
|
||||
@@ -63,7 +63,7 @@ ERA001.py:5:1: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
2 2 | # from foo import junk
|
||||
3 3 | #a = 3
|
||||
4 4 | a = 4
|
||||
@@ -82,7 +82,7 @@ ERA001.py:13:5: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
10 10 |
|
||||
11 11 | # This is a real comment.
|
||||
12 12 | # # This is a (nested) comment.
|
||||
@@ -100,7 +100,7 @@ ERA001.py:21:5: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
18 18 |
|
||||
19 19 | class A():
|
||||
20 20 | pass
|
||||
@@ -120,7 +120,7 @@ ERA001.py:26:5: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
23 23 |
|
||||
24 24 | dictionary = {
|
||||
25 25 | # "key1": 123, # noqa: ERA001
|
||||
@@ -139,7 +139,7 @@ ERA001.py:27:5: ERA001 Found commented-out code
|
||||
|
|
||||
= help: Remove commented-out code
|
||||
|
||||
ℹ Possible fix
|
||||
ℹ Display-only fix
|
||||
24 24 | dictionary = {
|
||||
25 25 | # "key1": 123, # noqa: ERA001
|
||||
26 26 | # "key2": 456,
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_python_ast::helpers::{pep_604_union, ReturnStatementVisitor};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr, ExprContext};
|
||||
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::{Definition, SemanticModel};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
/// Return the name of the function, if it's overloaded.
|
||||
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
||||
@@ -27,3 +36,81 @@ pub(crate) fn is_overload_impl(
|
||||
function.name.as_str() == overloaded_name
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a function, guess its return type.
|
||||
pub(crate) fn auto_return_type(
|
||||
function: &ast::StmtFunctionDef,
|
||||
target_version: PythonVersion,
|
||||
) -> Option<Expr> {
|
||||
// Collect all the `return` statements.
|
||||
let returns = {
|
||||
let mut visitor = ReturnStatementVisitor::default();
|
||||
visitor.visit_body(&function.body);
|
||||
if visitor.is_generator {
|
||||
return None;
|
||||
}
|
||||
visitor.returns
|
||||
};
|
||||
|
||||
// Determine the return type of the first `return` statement.
|
||||
let (return_statement, returns) = returns.split_first()?;
|
||||
let mut return_type = return_statement.value.as_deref().map_or(
|
||||
ResolvedPythonType::Atom(PythonType::None),
|
||||
ResolvedPythonType::from,
|
||||
);
|
||||
|
||||
// Merge the return types of the remaining `return` statements.
|
||||
for return_statement in returns {
|
||||
return_type = return_type.union(return_statement.value.as_deref().map_or(
|
||||
ResolvedPythonType::Atom(PythonType::None),
|
||||
ResolvedPythonType::from,
|
||||
));
|
||||
}
|
||||
|
||||
match return_type {
|
||||
ResolvedPythonType::Atom(python_type) => type_expr(python_type),
|
||||
ResolvedPythonType::Union(python_types) if target_version >= PythonVersion::Py310 => {
|
||||
// Aggregate all the individual types (e.g., `int`, `float`).
|
||||
let names = python_types
|
||||
.iter()
|
||||
.sorted_unstable()
|
||||
.filter_map(|python_type| type_expr(*python_type))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Wrap in a bitwise union (e.g., `int | float`).
|
||||
Some(pep_604_union(&names))
|
||||
}
|
||||
ResolvedPythonType::Union(_) => None,
|
||||
ResolvedPythonType::Unknown => None,
|
||||
ResolvedPythonType::TypeError => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a [`PythonType`], return an [`Expr`] that resolves to that type.
|
||||
fn type_expr(python_type: PythonType) -> Option<Expr> {
|
||||
fn name(name: &str) -> Expr {
|
||||
Expr::Name(ast::ExprName {
|
||||
id: name.into(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
})
|
||||
}
|
||||
|
||||
match python_type {
|
||||
PythonType::String => Some(name("str")),
|
||||
PythonType::Bytes => Some(name("bytes")),
|
||||
PythonType::Number(number) => match number {
|
||||
NumberLike::Integer => Some(name("int")),
|
||||
NumberLike::Float => Some(name("float")),
|
||||
NumberLike::Complex => Some(name("complex")),
|
||||
NumberLike::Bool => Some(name("bool")),
|
||||
},
|
||||
PythonType::None => Some(name("None")),
|
||||
PythonType::Ellipsis => None,
|
||||
PythonType::Dict => None,
|
||||
PythonType::List => None,
|
||||
PythonType::Set => None,
|
||||
PythonType::Tuple => None,
|
||||
PythonType::Generator => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,24 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_return_type() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_annotations/auto_return_type.py"),
|
||||
&LinterSettings {
|
||||
..LinterSettings::for_rules(vec![
|
||||
Rule::MissingReturnTypeUndocumentedPublicFunction,
|
||||
Rule::MissingReturnTypePrivateFunction,
|
||||
Rule::MissingReturnTypeSpecialMethod,
|
||||
Rule::MissingReturnTypeStaticMethod,
|
||||
Rule::MissingReturnTypeClassMethod,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suppress_none_returning() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Stmt};
|
||||
use ruff_python_parser::typing::parse_type_annotation;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
@@ -12,6 +12,7 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_annotations::helpers::auto_return_type;
|
||||
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
||||
|
||||
/// ## What it does
|
||||
@@ -41,7 +42,7 @@ pub struct MissingTypeFunctionArgument {
|
||||
impl Violation for MissingTypeFunctionArgument {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingTypeFunctionArgument { name } = self;
|
||||
let Self { name } = self;
|
||||
format!("Missing type annotation for function argument `{name}`")
|
||||
}
|
||||
}
|
||||
@@ -73,7 +74,7 @@ pub struct MissingTypeArgs {
|
||||
impl Violation for MissingTypeArgs {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingTypeArgs { name } = self;
|
||||
let Self { name } = self;
|
||||
format!("Missing type annotation for `*{name}`")
|
||||
}
|
||||
}
|
||||
@@ -105,7 +106,7 @@ pub struct MissingTypeKwargs {
|
||||
impl Violation for MissingTypeKwargs {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingTypeKwargs { name } = self;
|
||||
let Self { name } = self;
|
||||
format!("Missing type annotation for `**{name}`")
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ pub struct MissingTypeSelf {
|
||||
impl Violation for MissingTypeSelf {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingTypeSelf { name } = self;
|
||||
let Self { name } = self;
|
||||
format!("Missing type annotation for `{name}` in method")
|
||||
}
|
||||
}
|
||||
@@ -181,7 +182,7 @@ pub struct MissingTypeCls {
|
||||
impl Violation for MissingTypeCls {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingTypeCls { name } = self;
|
||||
let Self { name } = self;
|
||||
format!("Missing type annotation for `{name}` in classmethod")
|
||||
}
|
||||
}
|
||||
@@ -208,14 +209,26 @@ impl Violation for MissingTypeCls {
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypeUndocumentedPublicFunction {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingReturnTypeUndocumentedPublicFunction { name } = self;
|
||||
let Self { name, .. } = self;
|
||||
format!("Missing return type annotation for public function `{name}`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Self { annotation, .. } = self;
|
||||
if let Some(annotation) = annotation {
|
||||
Some(format!("Add return type annotation: `{annotation}`"))
|
||||
} else {
|
||||
Some(format!("Add return type annotation"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
@@ -240,14 +253,26 @@ impl Violation for MissingReturnTypeUndocumentedPublicFunction {
|
||||
#[violation]
|
||||
pub struct MissingReturnTypePrivateFunction {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypePrivateFunction {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingReturnTypePrivateFunction { name } = self;
|
||||
let Self { name, .. } = self;
|
||||
format!("Missing return type annotation for private function `{name}`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Self { annotation, .. } = self;
|
||||
if let Some(annotation) = annotation {
|
||||
Some(format!("Add return type annotation: `{annotation}`"))
|
||||
} else {
|
||||
Some(format!("Add return type annotation"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
@@ -285,17 +310,25 @@ impl Violation for MissingReturnTypePrivateFunction {
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeSpecialMethod {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for MissingReturnTypeSpecialMethod {
|
||||
impl Violation for MissingReturnTypeSpecialMethod {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingReturnTypeSpecialMethod { name } = self;
|
||||
let Self { name, .. } = self;
|
||||
format!("Missing return type annotation for special method `{name}`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add `None` return type".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Self { annotation, .. } = self;
|
||||
if let Some(annotation) = annotation {
|
||||
Some(format!("Add return type annotation: `{annotation}`"))
|
||||
} else {
|
||||
Some(format!("Add return type annotation"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,14 +358,26 @@ impl AlwaysFixableViolation for MissingReturnTypeSpecialMethod {
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeStaticMethod {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypeStaticMethod {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingReturnTypeStaticMethod { name } = self;
|
||||
let Self { name, .. } = self;
|
||||
format!("Missing return type annotation for staticmethod `{name}`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Self { annotation, .. } = self;
|
||||
if let Some(annotation) = annotation {
|
||||
Some(format!("Add return type annotation: `{annotation}`"))
|
||||
} else {
|
||||
Some(format!("Add return type annotation"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
@@ -361,14 +406,26 @@ impl Violation for MissingReturnTypeStaticMethod {
|
||||
#[violation]
|
||||
pub struct MissingReturnTypeClassMethod {
|
||||
name: String,
|
||||
annotation: Option<String>,
|
||||
}
|
||||
|
||||
impl Violation for MissingReturnTypeClassMethod {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingReturnTypeClassMethod { name } = self;
|
||||
let Self { name, .. } = self;
|
||||
format!("Missing return type annotation for classmethod `{name}`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Self { annotation, .. } = self;
|
||||
if let Some(annotation) = annotation {
|
||||
Some(format!("Add return type annotation: `{annotation}`"))
|
||||
} else {
|
||||
Some(format!("Add return type annotation"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
@@ -421,7 +478,7 @@ pub struct AnyType {
|
||||
impl Violation for AnyType {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let AnyType { name } = self;
|
||||
let Self { name } = self;
|
||||
format!("Dynamically typed expressions (typing.Any) are disallowed in `{name}`")
|
||||
}
|
||||
}
|
||||
@@ -673,21 +730,41 @@ pub(crate) fn definition(
|
||||
) {
|
||||
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
let return_type = auto_return_type(function, checker.settings.target_version)
|
||||
.map(|return_type| checker.generator().expr(&return_type));
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeClassMethod {
|
||||
name: name.to_string(),
|
||||
annotation: return_type.clone(),
|
||||
},
|
||||
function.identifier(),
|
||||
));
|
||||
);
|
||||
if let Some(return_type) = return_type {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
format!(" -> {return_type}"),
|
||||
function.parameters.range().end(),
|
||||
)));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
let return_type = auto_return_type(function, checker.settings.target_version)
|
||||
.map(|return_type| checker.generator().expr(&return_type));
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeStaticMethod {
|
||||
name: name.to_string(),
|
||||
annotation: return_type.clone(),
|
||||
},
|
||||
function.identifier(),
|
||||
));
|
||||
);
|
||||
if let Some(return_type) = return_type {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
format!(" -> {return_type}"),
|
||||
function.parameters.range().end(),
|
||||
)));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
} else if is_method && visibility::is_init(name) {
|
||||
// Allow omission of return annotation in `__init__` functions, as long as at
|
||||
@@ -697,6 +774,7 @@ pub(crate) fn definition(
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
annotation: Some("None".to_string()),
|
||||
},
|
||||
function.identifier(),
|
||||
);
|
||||
@@ -709,13 +787,15 @@ pub(crate) fn definition(
|
||||
}
|
||||
} else if is_method && visibility::is_magic(name) {
|
||||
if checker.enabled(Rule::MissingReturnTypeSpecialMethod) {
|
||||
let return_type = simple_magic_return_type(name);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
annotation: return_type.map(ToString::to_string),
|
||||
},
|
||||
function.identifier(),
|
||||
);
|
||||
if let Some(return_type) = simple_magic_return_type(name) {
|
||||
if let Some(return_type) = return_type {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
format!(" -> {return_type}"),
|
||||
function.parameters.range().end(),
|
||||
@@ -727,22 +807,44 @@ pub(crate) fn definition(
|
||||
match visibility {
|
||||
visibility::Visibility::Public => {
|
||||
if checker.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
let return_type =
|
||||
auto_return_type(function, checker.settings.target_version)
|
||||
.map(|return_type| checker.generator().expr(&return_type));
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: name.to_string(),
|
||||
annotation: return_type.clone(),
|
||||
},
|
||||
function.identifier(),
|
||||
));
|
||||
);
|
||||
if let Some(return_type) = return_type {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
format!(" -> {return_type}"),
|
||||
function.parameters.range().end(),
|
||||
)));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
visibility::Visibility::Private => {
|
||||
if checker.enabled(Rule::MissingReturnTypePrivateFunction) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
let return_type =
|
||||
auto_return_type(function, checker.settings.target_version)
|
||||
.map(|return_type| checker.generator().expr(&return_type));
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingReturnTypePrivateFunction {
|
||||
name: name.to_string(),
|
||||
annotation: return_type.clone(),
|
||||
},
|
||||
function.identifier(),
|
||||
));
|
||||
);
|
||||
if let Some(return_type) = return_type {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
format!(" -> {return_type}"),
|
||||
function.parameters.range().end(),
|
||||
)));
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@ allow_overload.py:29:9: ANN201 Missing return type annotation for public functio
|
||||
| ^^^ ANN201
|
||||
30 | return i
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
|
||||
---
|
||||
auto_return_type.py:1:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
1 | def func():
|
||||
| ^^^^ ANN201
|
||||
2 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-def func():
|
||||
1 |+def func() -> int:
|
||||
2 2 | return 1
|
||||
3 3 |
|
||||
4 4 |
|
||||
|
||||
auto_return_type.py:5:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
5 | def func():
|
||||
| ^^^^ ANN201
|
||||
6 | return 1.5
|
||||
|
|
||||
= help: Add return type annotation: `float`
|
||||
|
||||
ℹ Unsafe fix
|
||||
2 2 | return 1
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 |-def func():
|
||||
5 |+def func() -> float:
|
||||
6 6 | return 1.5
|
||||
7 7 |
|
||||
8 8 |
|
||||
|
||||
auto_return_type.py:9:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
9 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
10 | if x > 0:
|
||||
11 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `float`
|
||||
|
||||
ℹ Unsafe fix
|
||||
6 6 | return 1.5
|
||||
7 7 |
|
||||
8 8 |
|
||||
9 |-def func(x: int):
|
||||
9 |+def func(x: int) -> float:
|
||||
10 10 | if x > 0:
|
||||
11 11 | return 1
|
||||
12 12 | else:
|
||||
|
||||
auto_return_type.py:16:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
16 | def func():
|
||||
| ^^^^ ANN201
|
||||
17 | return True
|
||||
|
|
||||
= help: Add return type annotation: `bool`
|
||||
|
||||
ℹ Unsafe fix
|
||||
13 13 | return 1.5
|
||||
14 14 |
|
||||
15 15 |
|
||||
16 |-def func():
|
||||
16 |+def func() -> bool:
|
||||
17 17 | return True
|
||||
18 18 |
|
||||
19 19 |
|
||||
|
||||
auto_return_type.py:20:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
20 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
21 | if x > 0:
|
||||
22 | return None
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 | return True
|
||||
18 18 |
|
||||
19 19 |
|
||||
20 |-def func(x: int):
|
||||
20 |+def func(x: int) -> None:
|
||||
21 21 | if x > 0:
|
||||
22 22 | return None
|
||||
23 23 | else:
|
||||
|
||||
auto_return_type.py:27:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
27 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
28 | return 1 or 2.5 if x > 0 else 1.5 or "str"
|
||||
|
|
||||
= help: Add return type annotation: `str | float`
|
||||
|
||||
ℹ Unsafe fix
|
||||
24 24 | return
|
||||
25 25 |
|
||||
26 26 |
|
||||
27 |-def func(x: int):
|
||||
27 |+def func(x: int) -> str | float:
|
||||
28 28 | return 1 or 2.5 if x > 0 else 1.5 or "str"
|
||||
29 29 |
|
||||
30 30 |
|
||||
|
||||
auto_return_type.py:31:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
31 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
32 | return 1 + 2.5 if x > 0 else 1.5 or "str"
|
||||
|
|
||||
= help: Add return type annotation: `str | float`
|
||||
|
||||
ℹ Unsafe fix
|
||||
28 28 | return 1 or 2.5 if x > 0 else 1.5 or "str"
|
||||
29 29 |
|
||||
30 30 |
|
||||
31 |-def func(x: int):
|
||||
31 |+def func(x: int) -> str | float:
|
||||
32 32 | return 1 + 2.5 if x > 0 else 1.5 or "str"
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ annotation_presence.py:5:5: ANN201 Missing return type annotation for public fun
|
||||
| ^^^ ANN201
|
||||
6 | pass
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
annotation_presence.py:5:9: ANN001 Missing type annotation for function argument `a`
|
||||
|
|
||||
@@ -32,6 +33,7 @@ annotation_presence.py:10:5: ANN201 Missing return type annotation for public fu
|
||||
| ^^^ ANN201
|
||||
11 | pass
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
annotation_presence.py:10:17: ANN001 Missing type annotation for function argument `b`
|
||||
|
|
||||
@@ -56,6 +58,7 @@ annotation_presence.py:20:5: ANN201 Missing return type annotation for public fu
|
||||
| ^^^ ANN201
|
||||
21 | pass
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
annotation_presence.py:25:5: ANN201 Missing return type annotation for public function `foo`
|
||||
|
|
||||
@@ -64,6 +67,7 @@ annotation_presence.py:25:5: ANN201 Missing return type annotation for public fu
|
||||
| ^^^ ANN201
|
||||
26 | pass
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
annotation_presence.py:45:12: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
@@ -250,9 +254,9 @@ annotation_presence.py:159:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^ ANN204
|
||||
160 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
156 156 |
|
||||
157 157 | class Foo:
|
||||
158 158 | @decorator()
|
||||
@@ -270,9 +274,9 @@ annotation_presence.py:165:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^ ANN204
|
||||
166 | print(f"{self.attr=}")
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
162 162 |
|
||||
163 163 | # Regression test for: https://github.com/astral-sh/ruff/issues/7711
|
||||
164 164 | class Class:
|
||||
|
||||
@@ -7,6 +7,7 @@ ignore_fully_untyped.py:24:5: ANN201 Missing return type annotation for public f
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ ANN201
|
||||
25 | pass
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
ignore_fully_untyped.py:24:37: ANN001 Missing type annotation for function argument `b`
|
||||
|
|
||||
@@ -28,6 +29,7 @@ ignore_fully_untyped.py:32:5: ANN201 Missing return type annotation for public f
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ ANN201
|
||||
33 | pass
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
ignore_fully_untyped.py:43:9: ANN201 Missing return type annotation for public function `error_typed_self`
|
||||
|
|
||||
@@ -37,5 +39,6 @@ ignore_fully_untyped.py:43:9: ANN201 Missing return type annotation for public f
|
||||
| ^^^^^^^^^^^^^^^^ ANN201
|
||||
44 | pass
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ mypy_init_return.py:5:9: ANN204 [*] Missing return type annotation for special m
|
||||
| ^^^^^^^^ ANN204
|
||||
6 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
2 2 |
|
||||
3 3 | # Error
|
||||
4 4 | class Foo:
|
||||
@@ -29,9 +29,9 @@ mypy_init_return.py:11:9: ANN204 [*] Missing return type annotation for special
|
||||
| ^^^^^^^^ ANN204
|
||||
12 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
8 8 |
|
||||
9 9 | # Error
|
||||
10 10 | class Foo:
|
||||
@@ -48,6 +48,7 @@ mypy_init_return.py:40:5: ANN202 Missing return type annotation for private func
|
||||
| ^^^^^^^^ ANN202
|
||||
41 | ...
|
||||
|
|
||||
= help: Add return type annotation
|
||||
|
||||
mypy_init_return.py:47:9: ANN204 [*] Missing return type annotation for special method `__init__`
|
||||
|
|
||||
@@ -57,9 +58,9 @@ mypy_init_return.py:47:9: ANN204 [*] Missing return type annotation for special
|
||||
| ^^^^^^^^ ANN204
|
||||
48 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
44 44 | # Error – used to be ok for a moment since the mere presence
|
||||
45 45 | # of a vararg falsely indicated that the function has a typed argument.
|
||||
46 46 | class Foo:
|
||||
|
||||
@@ -8,9 +8,9 @@ simple_magic_methods.py:2:9: ANN204 [*] Missing return type annotation for speci
|
||||
| ^^^^^^^ ANN204
|
||||
3 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `str`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
1 1 | class Foo:
|
||||
2 |- def __str__(self):
|
||||
2 |+ def __str__(self) -> str:
|
||||
@@ -26,9 +26,9 @@ simple_magic_methods.py:5:9: ANN204 [*] Missing return type annotation for speci
|
||||
| ^^^^^^^^ ANN204
|
||||
6 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `str`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
2 2 | def __str__(self):
|
||||
3 3 | ...
|
||||
4 4 |
|
||||
@@ -46,9 +46,9 @@ simple_magic_methods.py:8:9: ANN204 [*] Missing return type annotation for speci
|
||||
| ^^^^^^^ ANN204
|
||||
9 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
5 5 | def __repr__(self):
|
||||
6 6 | ...
|
||||
7 7 |
|
||||
@@ -66,9 +66,9 @@ simple_magic_methods.py:11:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^^^^^^^^ ANN204
|
||||
12 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
8 8 | def __len__(self):
|
||||
9 9 | ...
|
||||
10 10 |
|
||||
@@ -86,9 +86,9 @@ simple_magic_methods.py:14:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^ ANN204
|
||||
15 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
11 11 | def __length_hint__(self):
|
||||
12 12 | ...
|
||||
13 13 |
|
||||
@@ -106,9 +106,9 @@ simple_magic_methods.py:17:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^ ANN204
|
||||
18 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
14 14 | def __init__(self):
|
||||
15 15 | ...
|
||||
16 16 |
|
||||
@@ -126,9 +126,9 @@ simple_magic_methods.py:20:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^ ANN204
|
||||
21 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `bool`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
17 17 | def __del__(self):
|
||||
18 18 | ...
|
||||
19 19 |
|
||||
@@ -146,9 +146,9 @@ simple_magic_methods.py:23:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^^ ANN204
|
||||
24 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `bytes`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
20 20 | def __bool__(self):
|
||||
21 21 | ...
|
||||
22 22 |
|
||||
@@ -166,9 +166,9 @@ simple_magic_methods.py:26:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^^^ ANN204
|
||||
27 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `str`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
23 23 | def __bytes__(self):
|
||||
24 24 | ...
|
||||
25 25 |
|
||||
@@ -186,9 +186,9 @@ simple_magic_methods.py:29:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^^^^^ ANN204
|
||||
30 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `bool`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
26 26 | def __format__(self, format_spec):
|
||||
27 27 | ...
|
||||
28 28 |
|
||||
@@ -206,9 +206,9 @@ simple_magic_methods.py:32:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^^^^ ANN204
|
||||
33 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `complex`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
29 29 | def __contains__(self, item):
|
||||
30 30 | ...
|
||||
31 31 |
|
||||
@@ -226,9 +226,9 @@ simple_magic_methods.py:35:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^ ANN204
|
||||
36 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
32 32 | def __complex__(self):
|
||||
33 33 | ...
|
||||
34 34 |
|
||||
@@ -246,9 +246,9 @@ simple_magic_methods.py:38:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^^ ANN204
|
||||
39 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `float`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
35 35 | def __int__(self):
|
||||
36 36 | ...
|
||||
37 37 |
|
||||
@@ -266,9 +266,9 @@ simple_magic_methods.py:41:9: ANN204 [*] Missing return type annotation for spec
|
||||
| ^^^^^^^^^ ANN204
|
||||
42 | ...
|
||||
|
|
||||
= help: Add `None` return type
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
38 38 | def __float__(self):
|
||||
39 39 | ...
|
||||
40 40 |
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
|
||||
---
|
||||
suppress_none_returning.py:45:5: ANN201 Missing return type annotation for public function `foo`
|
||||
suppress_none_returning.py:45:5: ANN201 [*] Missing return type annotation for public function `foo`
|
||||
|
|
||||
44 | # Error
|
||||
45 | def foo():
|
||||
| ^^^ ANN201
|
||||
46 | return True
|
||||
|
|
||||
= help: Add return type annotation: `bool`
|
||||
|
||||
suppress_none_returning.py:50:5: ANN201 Missing return type annotation for public function `foo`
|
||||
ℹ Unsafe fix
|
||||
42 42 |
|
||||
43 43 |
|
||||
44 44 | # Error
|
||||
45 |-def foo():
|
||||
45 |+def foo() -> bool:
|
||||
46 46 | return True
|
||||
47 47 |
|
||||
48 48 |
|
||||
|
||||
suppress_none_returning.py:50:5: ANN201 [*] Missing return type annotation for public function `foo`
|
||||
|
|
||||
49 | # Error
|
||||
50 | def foo():
|
||||
@@ -17,6 +28,17 @@ suppress_none_returning.py:50:5: ANN201 Missing return type annotation for publi
|
||||
51 | a = 2 + 2
|
||||
52 | if a == 4:
|
||||
|
|
||||
= help: Add return type annotation: `bool | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
47 47 |
|
||||
48 48 |
|
||||
49 49 | # Error
|
||||
50 |-def foo():
|
||||
50 |+def foo() -> bool | None:
|
||||
51 51 | a = 2 + 2
|
||||
52 52 | if a == 4:
|
||||
53 53 | return True
|
||||
|
||||
suppress_none_returning.py:59:9: ANN001 Missing type annotation for function argument `a`
|
||||
|
|
||||
|
||||
@@ -28,6 +28,7 @@ mod tests {
|
||||
#[test_case(Rule::HardcodedTempFile, Path::new("S108.py"))]
|
||||
#[test_case(Rule::HashlibInsecureHashFunction, Path::new("S324.py"))]
|
||||
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"))]
|
||||
#[test_case(Rule::MakoTemplates, Path::new("S702.py"))]
|
||||
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"))]
|
||||
#[test_case(Rule::ParamikoCall, Path::new("S601.py"))]
|
||||
#[test_case(Rule::RequestWithNoCertValidation, Path::new("S501.py"))]
|
||||
|
||||
@@ -13,7 +13,15 @@ use crate::checkers::ast::Checker;
|
||||
use super::super::helpers::string_literal;
|
||||
|
||||
static SQL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?i)\b(select\s.+\sfrom\s|delete\s+from\s|(insert|replace)\s.+\svalues\s|update\s.+\sset\s)")
|
||||
// We pass this generated expression strings like:
|
||||
// "SELECT " + val + " FROM " + table
|
||||
// f'delete from table where var = {var}'
|
||||
// "\n SELECT *\n FROM table\n WHERE var = {}\n ".format(var)
|
||||
//
|
||||
// To avoid false positives, we:
|
||||
// - Require the SQL to be at the start of the expression, allowing for tokens that are not a part of the string
|
||||
// - Require whole-word matches for SQL keywords
|
||||
Regex::new(r#"(?i)\A(\"|f\"|\'|f\'|\\|\\n|\s)*\b(select\s.+\sfrom\s|delete\s+from\s|(insert|replace)\s.+\svalues\s|update\s.+\sset\s)"#)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
@@ -51,7 +59,7 @@ fn has_string_literal(expr: &Expr) -> bool {
|
||||
}
|
||||
|
||||
fn matches_sql_statement(string: &str) -> bool {
|
||||
SQL_REGEX.is_match(string)
|
||||
SQL_REGEX.is_match(string.trim_start())
|
||||
}
|
||||
|
||||
fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of the `mako` templates.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Mako templates allow HTML and JavaScript rendering by default, and are
|
||||
/// inherently open to XSS attacks. Ensure variables in all templates are
|
||||
/// properly sanitized via the `n`, `h` or `x` flags (depending on context).
|
||||
/// For example, to HTML escape the variable `data`, use `${ data |h }`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from mako.template import Template
|
||||
///
|
||||
/// Template("hello")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from mako.template import Template
|
||||
///
|
||||
/// Template("hello |h")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Mako documentation](https://www.makotemplates.org/)
|
||||
/// - [OpenStack security: Cross site scripting XSS](https://security.openstack.org/guidelines/dg_cross-site-scripting-xss.html)
|
||||
/// - [Common Weakness Enumeration: CWE-80](https://cwe.mitre.org/data/definitions/80.html)
|
||||
#[violation]
|
||||
pub struct MakoTemplates;
|
||||
|
||||
impl Violation for MakoTemplates {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Mako templates allow HTML and JavaScript rendering by default and are inherently open to XSS attacks"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// S702
|
||||
pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["mako", "template", "Template"]))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(MakoTemplates, call.func.range()));
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ pub(crate) use hardcoded_tmp_directory::*;
|
||||
pub(crate) use hashlib_insecure_hash_functions::*;
|
||||
pub(crate) use jinja2_autoescape_false::*;
|
||||
pub(crate) use logging_config_insecure_listen::*;
|
||||
pub(crate) use mako_templates::*;
|
||||
pub(crate) use paramiko_calls::*;
|
||||
pub(crate) use request_with_no_cert_validation::*;
|
||||
pub(crate) use request_without_timeout::*;
|
||||
@@ -37,6 +38,7 @@ mod hardcoded_tmp_directory;
|
||||
mod hashlib_insecure_hash_functions;
|
||||
mod jinja2_autoescape_false;
|
||||
mod logging_config_insecure_listen;
|
||||
mod mako_templates;
|
||||
mod paramiko_calls;
|
||||
mod request_with_no_cert_validation;
|
||||
mod request_without_timeout;
|
||||
|
||||
@@ -476,7 +476,7 @@ S608.py:102:9: S608 Possible SQL injection vector through string-based query con
|
||||
102 | query = "REPLACE table VALUES (%s)" % (var,)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S608
|
||||
103 |
|
||||
104 | query = "Deselect something that is not SQL even though it has a ' from ' somewhere in %s." % "there"
|
||||
104 | not_a_query = "Deselect something that is not SQL even though it has a ' from ' somewhere in %s." % "there"
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S702.py:6:1: S702 Mako templates allow HTML and JavaScript rendering by default and are inherently open to XSS attacks
|
||||
|
|
||||
6 | Template("hello")
|
||||
| ^^^^^^^^ S702
|
||||
7 |
|
||||
8 | mako.template.Template("hern")
|
||||
|
|
||||
|
||||
S702.py:8:1: S702 Mako templates allow HTML and JavaScript rendering by default and are inherently open to XSS attacks
|
||||
|
|
||||
6 | Template("hello")
|
||||
7 |
|
||||
8 | mako.template.Template("hern")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ S702
|
||||
9 | template.Template("hern")
|
||||
|
|
||||
|
||||
S702.py:9:1: S702 Mako templates allow HTML and JavaScript rendering by default and are inherently open to XSS attacks
|
||||
|
|
||||
8 | mako.template.Template("hern")
|
||||
9 | template.Template("hern")
|
||||
| ^^^^^^^^^^^^^^^^^ S702
|
||||
|
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ mod tests {
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
@@ -25,4 +26,22 @@ mod tests {
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::BooleanTypeHintPositionalArgument, Path::new("FBT.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_boolean_trap").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::collect_call_path;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -26,6 +27,9 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// keyword-only argument, to force callers to be explicit when providing
|
||||
/// the argument.
|
||||
///
|
||||
/// In [preview], this rule will also flag annotations that include boolean
|
||||
/// variants, like `bool | int`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from math import ceil, floor
|
||||
@@ -86,6 +90,8 @@ use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
/// ## References
|
||||
/// - [Python documentation: Calls](https://docs.python.org/3/reference/expressions.html#calls)
|
||||
/// - [_How to Avoid “The Boolean Trap”_ by Adam Johnson](https://adamj.eu/tech/2021/07/10/python-type-hints-how-to-avoid-the-boolean-trap/)
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[violation]
|
||||
pub struct BooleanTypeHintPositionalArgument;
|
||||
|
||||
@@ -96,6 +102,7 @@ impl Violation for BooleanTypeHintPositionalArgument {
|
||||
}
|
||||
}
|
||||
|
||||
/// FBT001
|
||||
pub(crate) fn boolean_type_hint_positional_argument(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
@@ -122,15 +129,17 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
let Some(annotation) = parameter.annotation.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// check for both bool (python class) and 'bool' (string annotation)
|
||||
let hint = match annotation.as_ref() {
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
|
||||
_ => false,
|
||||
};
|
||||
if !hint || !checker.semantic().is_builtin("bool") {
|
||||
continue;
|
||||
if checker.settings.preview.is_enabled() {
|
||||
if !match_annotation_to_complex_bool(annotation, checker.semantic()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if !match_annotation_to_literal_bool(annotation) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if !checker.semantic().is_builtin("bool") {
|
||||
return;
|
||||
}
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BooleanTypeHintPositionalArgument,
|
||||
@@ -138,3 +147,52 @@ pub(crate) fn boolean_type_hint_positional_argument(
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the annotation is a boolean type hint (e.g., `bool`).
|
||||
fn match_annotation_to_literal_bool(annotation: &Expr) -> bool {
|
||||
match annotation {
|
||||
// Ex) `True`
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
// Ex) `"True"`
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the annotation is a boolean type hint (e.g., `bool`), or a type hint that
|
||||
/// includes boolean as a variant (e.g., `bool | int`).
|
||||
fn match_annotation_to_complex_bool(annotation: &Expr, semantic: &SemanticModel) -> bool {
|
||||
match annotation {
|
||||
// Ex) `bool`
|
||||
Expr::Name(name) => &name.id == "bool",
|
||||
// Ex) `"bool"`
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value == "bool",
|
||||
// Ex) `bool | int`
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op: ast::Operator::BitOr,
|
||||
right,
|
||||
..
|
||||
}) => {
|
||||
match_annotation_to_complex_bool(left, semantic)
|
||||
|| match_annotation_to_complex_bool(right, semantic)
|
||||
}
|
||||
// Ex) `typing.Union[bool, int]`
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
if semantic.match_typing_expr(value, "Union") {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
elts.iter()
|
||||
.any(|elt| match_annotation_to_complex_bool(elt, semantic))
|
||||
} else {
|
||||
// Union with a single type is an invalid type annotation
|
||||
false
|
||||
}
|
||||
} else if semantic.match_typing_expr(value, "Optional") {
|
||||
match_annotation_to_complex_bool(slice, semantic)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_boolean_trap/mod.rs
|
||||
---
|
||||
FBT.py:4:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
2 | posonly_nohint,
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
| ^^^^^^^^^^^^^^^^ FBT001
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
6 | /,
|
||||
|
|
||||
|
||||
FBT.py:5:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
3 | posonly_nonboolhint: int,
|
||||
4 | posonly_boolhint: bool,
|
||||
5 | posonly_boolstrhint: "bool",
|
||||
| ^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
6 | /,
|
||||
7 | offset,
|
||||
|
|
||||
|
||||
FBT.py:10:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
8 | posorkw_nonvalued_nohint,
|
||||
9 | posorkw_nonvalued_nonboolhint: int,
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
|
|
||||
|
||||
FBT.py:11:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
9 | posorkw_nonvalued_nonboolhint: int,
|
||||
10 | posorkw_nonvalued_boolhint: bool,
|
||||
11 | posorkw_nonvalued_boolstrhint: "bool",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
|
|
||||
|
||||
FBT.py:14:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
12 | posorkw_boolvalued_nohint=True,
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
|
|
||||
|
||||
FBT.py:15:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
13 | posorkw_boolvalued_nonboolhint: int = True,
|
||||
14 | posorkw_boolvalued_boolhint: bool = True,
|
||||
15 | posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
|
|
||||
|
||||
FBT.py:18:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
16 | posorkw_nonboolvalued_nohint=1,
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||
20 | *,
|
||||
|
|
||||
|
||||
FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
17 | posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
18 | posorkw_nonboolvalued_boolhint: bool = 3,
|
||||
19 | posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FBT001
|
||||
20 | *,
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:89:19: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
88 | # FBT001: Boolean positional arg in function definition
|
||||
89 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^ FBT001
|
||||
90 | pass
|
||||
|
|
||||
|
||||
FBT.py:99:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
99 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||
| ^ FBT001
|
||||
100 | pass
|
||||
|
|
||||
|
||||
FBT.py:103:10: FBT001 Boolean-typed positional argument in function definition
|
||||
|
|
||||
103 | def func(x: bool | str):
|
||||
| ^ FBT001
|
||||
104 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ B004.py:3:8: B004 [*] Using `hasattr(x, "__call__")` to test if x is callable is
|
||||
|
|
||||
= help: Replace with `callable()`
|
||||
|
||||
ℹ Fix
|
||||
ℹ Safe fix
|
||||
1 1 | def this_is_a_bug():
|
||||
2 2 | o = object()
|
||||
3 |- if hasattr(o, "__call__"):
|
||||
|
||||
@@ -12,7 +12,7 @@ B006_1.py:3:22: B006 [*] Do not use mutable data structures for argument default
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
1 1 | # Docstring followed by a newline
|
||||
2 2 |
|
||||
3 |-def foobar(foor, bar={}):
|
||||
|
||||
@@ -12,7 +12,7 @@ B006_2.py:4:22: B006 [*] Do not use mutable data structures for argument default
|
||||
|
|
||||
= help: Replace with `None`; initialize within function
|
||||
|
||||
ℹ Suggested fix
|
||||
ℹ Unsafe fix
|
||||
1 1 | # Docstring followed by whitespace with no newline
|
||||
2 2 | # Regression test for https://github.com/astral-sh/ruff/issues/7155
|
||||
3 3 |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user