Compare commits
45 Commits
micha/sema
...
jack/nonlo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f22497bdd | ||
|
|
05cf7c3458 | ||
|
|
664a9a28dc | ||
|
|
965f415212 | ||
|
|
83b5bbf004 | ||
|
|
87f6f08ef5 | ||
|
|
59114d0301 | ||
|
|
492f5bf2aa | ||
|
|
934aaa23f3 | ||
|
|
59aa869724 | ||
|
|
edaffa6c4f | ||
|
|
5fb2fb916b | ||
|
|
801f69a7b4 | ||
|
|
3926dd8424 | ||
|
|
563268ce53 | ||
|
|
221edcba5c | ||
|
|
beb98dae7c | ||
|
|
05b1b788a0 | ||
|
|
a18f76158d | ||
|
|
8f400bb37a | ||
|
|
1eff0300d3 | ||
|
|
fea84e8777 | ||
|
|
79fe538458 | ||
|
|
f7234cb474 | ||
|
|
35a33f045e | ||
|
|
f32f7a3b48 | ||
|
|
68106dd631 | ||
|
|
ab3af924ef | ||
|
|
05139a323b | ||
|
|
5eb5ec987d | ||
|
|
1a099886ab | ||
|
|
a8f2c26143 | ||
|
|
fda188953f | ||
|
|
546f1b7b39 | ||
|
|
7533a0bfdb | ||
|
|
3ee3434187 | ||
|
|
149350bf39 | ||
|
|
6a42d28867 | ||
|
|
ce2bdb9357 | ||
|
|
d78d10dd94 | ||
|
|
36276143be | ||
|
|
2643dc5b7a | ||
|
|
738692baff | ||
|
|
9a4b85d845 | ||
|
|
6d8c84bde9 |
1
.github/workflows/mypy_primer.yaml
vendored
1
.github/workflows/mypy_primer.yaml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
- ".github/workflows/mypy_primer.yaml"
|
||||
- ".github/workflows/mypy_primer_comment.yaml"
|
||||
- "Cargo.lock"
|
||||
- "!**.md"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
|
||||
76
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
76
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -17,6 +17,7 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
@@ -63,32 +64,75 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@9c34dc514ee9aef6735db1dfebb80f63acbc3440"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@f0eec0e549684d8e1d7b8bc3e351202124b63bda"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--projects ruff/projects_old.txt \
|
||||
--commit old_commit \
|
||||
--output diagnostics_old.json
|
||||
diff \
|
||||
--projects-old ruff/projects_old.txt \
|
||||
--projects-new ruff/projects_new.txt \
|
||||
--old old_commit \
|
||||
--new new_commit \
|
||||
--output-old diagnostics-old.json \
|
||||
--output-new diagnostics-new.json
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--projects ruff/projects_new.txt \
|
||||
--commit new_commit \
|
||||
--output diagnostics_new.json
|
||||
mkdir dist
|
||||
|
||||
ecosystem-analyzer \
|
||||
generate-diff \
|
||||
diagnostics_old.json \
|
||||
diagnostics_new.json \
|
||||
diagnostics-old.json \
|
||||
diagnostics-new.json \
|
||||
--old-name "main (merge base)" \
|
||||
--new-name "$REF_NAME" \
|
||||
--output-html diff.html
|
||||
--output-html dist/diff.html
|
||||
|
||||
- name: Upload HTML diff report
|
||||
ecosystem-analyzer \
|
||||
generate-diff-statistics \
|
||||
diagnostics-old.json \
|
||||
diagnostics-new.json \
|
||||
--old-name "main (merge base)" \
|
||||
--new-name "$REF_NAME" \
|
||||
--output diff-statistics.md
|
||||
|
||||
echo '## `ecosystem-analyzer` results' > comment.md
|
||||
echo >> comment.md
|
||||
cat diff-statistics.md >> comment.md
|
||||
|
||||
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
- name: "Append deployment URL"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
env:
|
||||
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||
run: |
|
||||
echo >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)**" >> comment.md
|
||||
|
||||
- name: Upload comment
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: comment.md
|
||||
path: comment.md
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- name: Upload diagnostics diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: diff.html
|
||||
path: diff.html
|
||||
path: dist/diff.html
|
||||
|
||||
85
.github/workflows/ty-ecosystem-analyzer_comment.yaml
vendored
Normal file
85
.github/workflows/ty-ecosystem-analyzer_comment.yaml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: PR comment (ty ecosystem-analyzer)
|
||||
|
||||
on: # zizmor: ignore[dangerous-triggers]
|
||||
workflow_run:
|
||||
workflows: [ty ecosystem-analyzer]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
workflow_run_id:
|
||||
description: The ty ecosystem-analyzer workflow that triggers the workflow run
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: Download PR number
|
||||
with:
|
||||
name: pr-number
|
||||
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Parse pull request number
|
||||
id: pr-number
|
||||
run: |
|
||||
if [[ -f pr-number ]]
|
||||
then
|
||||
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: "Download comment.md"
|
||||
id: download-comment
|
||||
if: steps.pr-number.outputs.pr-number
|
||||
with:
|
||||
name: comment.md
|
||||
workflow: ty-ecosystem-analyzer.yaml
|
||||
pr: ${{ steps.pr-number.outputs.pr-number }}
|
||||
path: pr/comment
|
||||
workflow_conclusion: completed
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Generate comment content
|
||||
id: generate-comment
|
||||
if: ${{ steps.download-comment.outputs.found_artifact == 'true' }}
|
||||
run: |
|
||||
# Guard against malicious ty ecosystem-analyzer results that symlink to a secret
|
||||
# file on this runner
|
||||
if [[ -L pr/comment/comment.md ]]
|
||||
then
|
||||
echo "Error: comment.md cannot be a symlink"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Note: this identifier is used to find the comment to update on subsequent runs
|
||||
echo '<!-- generated-comment ty ecosystem-analyzer -->' > comment.md
|
||||
echo >> comment.md
|
||||
cat pr/comment/comment.md >> comment.md
|
||||
|
||||
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
|
||||
cat comment.md >> "$GITHUB_OUTPUT"
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
|
||||
if: steps.generate-comment.outcome == 'success'
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: "<!-- generated-comment ty ecosystem-analyzer -->"
|
||||
|
||||
- name: Create or update comment
|
||||
if: steps.find-comment.outcome == 'success'
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.pr-number }}
|
||||
body-path: comment.md
|
||||
edit-mode: replace
|
||||
1
.github/zizmor.yml
vendored
1
.github/zizmor.yml
vendored
@@ -10,6 +10,7 @@ rules:
|
||||
ignore:
|
||||
- build-docker.yml
|
||||
- publish-playground.yml
|
||||
- ty-ecosystem-analyzer.yaml
|
||||
excessive-permissions:
|
||||
# it's hard to test what the impact of removing these ignores would be
|
||||
# without actually running the release workflow...
|
||||
|
||||
@@ -6,7 +6,7 @@ exclude: |
|
||||
crates/ty_vendored/vendor/.*|
|
||||
crates/ty_project/resources/.*|
|
||||
crates/ty_python_semantic/resources/corpus/.*|
|
||||
crates/ty/docs/(configuration|rules|cli).md|
|
||||
crates/ty/docs/(configuration|rules|cli|environment).md|
|
||||
crates/ruff_benchmark/resources/.*|
|
||||
crates/ruff_linter/resources/.*|
|
||||
crates/ruff_linter/src/rules/.*/snapshots/.*|
|
||||
|
||||
76
Cargo.lock
generated
76
Cargo.lock
generated
@@ -680,11 +680,6 @@ name = "countme"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
dependencies = [
|
||||
"dashmap 5.5.3",
|
||||
"once_cell",
|
||||
"rustc-hash 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
@@ -852,19 +847,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
@@ -2262,7 +2244,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
"regex",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
@@ -2772,7 +2754,7 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_workspace",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
@@ -2818,7 +2800,7 @@ dependencies = [
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tikv-jemallocator",
|
||||
@@ -2847,7 +2829,7 @@ dependencies = [
|
||||
"arc-swap",
|
||||
"camino",
|
||||
"countme",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"dunce",
|
||||
"etcetera",
|
||||
"filetime",
|
||||
@@ -2866,7 +2848,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2874,6 +2856,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ty_static",
|
||||
"web-time",
|
||||
"zip",
|
||||
]
|
||||
@@ -2917,6 +2900,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ty",
|
||||
"ty_project",
|
||||
"ty_static",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -2938,7 +2922,7 @@ dependencies = [
|
||||
"ruff_cache",
|
||||
"ruff_macros",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
@@ -3020,7 +3004,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3092,7 +3076,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -3142,7 +3126,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -3191,7 +3175,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"static_assertions",
|
||||
@@ -3215,7 +3199,7 @@ dependencies = [
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"smallvec",
|
||||
@@ -3276,7 +3260,7 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_workspace",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
@@ -3365,7 +3349,7 @@ dependencies = [
|
||||
"ruff_python_semantic",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"shellexpand",
|
||||
@@ -3384,12 +3368,6 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -3444,7 +3422,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa-macro-rules",
|
||||
"salsa-macros",
|
||||
"smallvec",
|
||||
@@ -4142,7 +4120,6 @@ dependencies = [
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
"colored 3.0.0",
|
||||
"countme",
|
||||
"crossbeam",
|
||||
"ctrlc",
|
||||
"dunce",
|
||||
@@ -4165,6 +4142,7 @@ dependencies = [
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_server",
|
||||
"ty_static",
|
||||
"wild",
|
||||
]
|
||||
|
||||
@@ -4178,7 +4156,7 @@ dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
@@ -4210,7 +4188,7 @@ dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4231,7 +4209,6 @@ dependencies = [
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"compact_str",
|
||||
"countme",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
@@ -4255,7 +4232,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4268,6 +4245,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_test",
|
||||
"ty_vendored",
|
||||
]
|
||||
@@ -4286,7 +4264,7 @@ dependencies = [
|
||||
"ruff_notebook",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4299,6 +4277,13 @@ dependencies = [
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_static"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"ruff_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_test"
|
||||
version = "0.0.0"
|
||||
@@ -4317,7 +4302,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"rustc-stable-hash",
|
||||
"salsa",
|
||||
"serde",
|
||||
@@ -4327,6 +4312,7 @@ dependencies = [
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -44,6 +44,7 @@ ty_ide = { path = "crates/ty_ide" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
ty_static = { path = "crates/ty_static" }
|
||||
ty_test = { path = "crates/ty_test" }
|
||||
ty_vendored = { path = "crates/ty_vendored" }
|
||||
|
||||
@@ -83,7 +84,7 @@ get-size2 = { version = "0.5.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
"compact-str"
|
||||
"compact-str",
|
||||
] }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
@@ -173,7 +174,7 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features =
|
||||
"env-filter",
|
||||
"fmt",
|
||||
"ansi",
|
||||
"smallvec"
|
||||
"smallvec",
|
||||
] }
|
||||
tryfn = { version = "0.2.1" }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
@@ -183,11 +184,7 @@ unicode-width = { version = "0.2.0" }
|
||||
unicode_names2 = { version = "1.2.2" }
|
||||
unicode-normalization = { version = "0.1.23" }
|
||||
url = { version = "2.5.0" }
|
||||
uuid = { version = "1.6.1", features = [
|
||||
"v4",
|
||||
"fast-rng",
|
||||
"macro-diagnostics",
|
||||
] }
|
||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
|
||||
walkdir = { version = "2.3.2" }
|
||||
wasm-bindgen = { version = "0.2.92" }
|
||||
wasm-bindgen-test = { version = "0.3.42" }
|
||||
@@ -222,8 +219,8 @@ must_use_candidate = "allow"
|
||||
similar_names = "allow"
|
||||
single_match_else = "allow"
|
||||
too_many_lines = "allow"
|
||||
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
|
||||
unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often.
|
||||
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
|
||||
unnecessary_debug_formatting = "allow" # too many instances, the display also doesn't quote the path which is often desired in logs where we use them the most often.
|
||||
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
|
||||
needless_raw_string_hashes = "allow"
|
||||
# Disallowed restriction lints
|
||||
|
||||
@@ -430,6 +430,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Babel](https://github.com/python-babel/babel)
|
||||
- Benchling ([Refac](https://github.com/benchling/refac))
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- Capital One ([datacompy](https://github.com/capitalone/datacompy))
|
||||
- CrowdCent ([NumerBlox](https://github.com/crowdcent/numerblox)) <!-- typos: ignore -->
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- CERN ([Indico](https://getindico.io/))
|
||||
|
||||
@@ -681,7 +681,7 @@ mod tests {
|
||||
UnsafeFixes::Enabled,
|
||||
)
|
||||
.unwrap();
|
||||
if diagnostics.inner.iter().any(Diagnostic::is_syntax_error) {
|
||||
if diagnostics.inner.iter().any(Diagnostic::is_invalid_syntax) {
|
||||
parse_errors.push(path.clone());
|
||||
}
|
||||
paths.push(path);
|
||||
|
||||
@@ -9,15 +9,15 @@ use ignore::Error;
|
||||
use log::{debug, error, warn};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use ruff_linter::message::diagnostic_from_violation;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::panic::catch_unwind;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{LinterSettings, flags};
|
||||
use ruff_linter::{IOError, fs, warn_user_once};
|
||||
use ruff_linter::{IOError, Violation, fs, warn_user_once};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_workspace::resolver::{
|
||||
@@ -129,11 +129,7 @@ pub(crate) fn check(
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
|
||||
Diagnostics::new(
|
||||
vec![diagnostic_from_violation(
|
||||
IOError { message },
|
||||
TextRange::default(),
|
||||
&dummy,
|
||||
)],
|
||||
vec![IOError { message }.into_diagnostic(TextRange::default(), &dummy)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
@@ -166,7 +162,9 @@ pub(crate) fn check(
|
||||
|a, b| (a.0 + b.0, a.1 + b.1),
|
||||
);
|
||||
|
||||
all_diagnostics.inner.sort();
|
||||
all_diagnostics
|
||||
.inner
|
||||
.sort_by(Diagnostic::ruff_start_ordering);
|
||||
|
||||
// Store the caches.
|
||||
caches.persist()?;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::packaging;
|
||||
use ruff_linter::settings::flags;
|
||||
@@ -52,6 +53,8 @@ pub(crate) fn check_stdin(
|
||||
noqa,
|
||||
fix_mode,
|
||||
)?;
|
||||
diagnostics.inner.sort_unstable();
|
||||
diagnostics
|
||||
.inner
|
||||
.sort_unstable_by(Diagnostic::ruff_start_ordering);
|
||||
Ok(diagnostics)
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ use log::{debug, warn};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_linter::codes::Rule;
|
||||
use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only};
|
||||
use ruff_linter::message::{create_syntax_error_diagnostic, diagnostic_from_violation};
|
||||
use ruff_linter::message::create_syntax_error_diagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{LinterSettings, flags};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{IOError, fs};
|
||||
use ruff_linter::{IOError, Violation, fs};
|
||||
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
@@ -62,13 +62,12 @@ impl Diagnostics {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let source_file = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![diagnostic_from_violation(
|
||||
vec![
|
||||
IOError {
|
||||
message: err.to_string(),
|
||||
},
|
||||
TextRange::default(),
|
||||
&source_file,
|
||||
)],
|
||||
}
|
||||
.into_diagnostic(TextRange::default(), &source_file),
|
||||
],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -20,6 +20,7 @@ ruff_python_parser = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_source_file = { workspace = true, features = ["get-size"] }
|
||||
ruff_text_size = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
|
||||
anstyle = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
|
||||
@@ -83,7 +83,7 @@ impl Diagnostic {
|
||||
///
|
||||
/// Note that `message` is stored in the primary annotation, _not_ in the primary diagnostic
|
||||
/// message.
|
||||
pub fn syntax_error(
|
||||
pub fn invalid_syntax(
|
||||
span: impl Into<Span>,
|
||||
message: impl IntoDiagnosticMessage,
|
||||
range: impl Ranged,
|
||||
@@ -365,7 +365,7 @@ impl Diagnostic {
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a syntax error message.
|
||||
pub fn is_syntax_error(&self) -> bool {
|
||||
pub fn is_invalid_syntax(&self) -> bool {
|
||||
self.id().is_invalid_syntax()
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ impl Diagnostic {
|
||||
|
||||
/// Returns the URL for the rule documentation, if it exists.
|
||||
pub fn to_url(&self) -> Option<String> {
|
||||
if self.is_syntax_error() {
|
||||
if self.is_invalid_syntax() {
|
||||
None
|
||||
} else {
|
||||
Some(format!(
|
||||
@@ -447,20 +447,16 @@ impl Diagnostic {
|
||||
pub fn expect_range(&self) -> TextRange {
|
||||
self.range().expect("Expected a range for the primary span")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Diagnostic {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Diagnostic {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(
|
||||
(self.ruff_source_file()?, self.range()?.start())
|
||||
.cmp(&(other.ruff_source_file()?, other.range()?.start())),
|
||||
)
|
||||
/// Returns the ordering of diagnostics based on the start of their ranges, if they have any.
|
||||
///
|
||||
/// Panics if either diagnostic has no primary span, if the span has no range, or if its file is
|
||||
/// not a `SourceFile`.
|
||||
pub fn ruff_start_ordering(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(self.expect_ruff_source_file(), self.expect_range().start()).cmp(&(
|
||||
other.expect_ruff_source_file(),
|
||||
other.expect_range().start(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use ruff_python_ast::PythonVersion;
|
||||
use rustc_hash::FxHasher;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::num::NonZeroUsize;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
pub mod diagnostic;
|
||||
pub mod display;
|
||||
@@ -50,8 +51,8 @@ pub trait Db: salsa::Database {
|
||||
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
|
||||
/// watching the files for changes.
|
||||
pub fn max_parallelism() -> NonZeroUsize {
|
||||
std::env::var("TY_MAX_PARALLELISM")
|
||||
.or_else(|_| std::env::var("RAYON_NUM_THREADS"))
|
||||
std::env::var(EnvVars::TY_MAX_PARALLELISM)
|
||||
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_else(|| {
|
||||
|
||||
@@ -13,6 +13,7 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ty_static = { workspace = true }
|
||||
ruff = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
ruff_linter = { workspace = true, features = ["schemars"] }
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
generate_cli_help, generate_docs, generate_json_schema, generate_ty_cli_reference,
|
||||
generate_ty_options, generate_ty_rules, generate_ty_schema,
|
||||
generate_ty_env_vars_reference, generate_ty_options, generate_ty_rules, generate_ty_schema,
|
||||
};
|
||||
|
||||
pub(crate) const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all";
|
||||
@@ -44,5 +44,8 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
generate_ty_options::main(&generate_ty_options::Args { mode: args.mode })?;
|
||||
generate_ty_rules::main(&generate_ty_rules::Args { mode: args.mode })?;
|
||||
generate_ty_cli_reference::main(&generate_ty_cli_reference::Args { mode: args.mode })?;
|
||||
generate_ty_env_vars_reference::main(&generate_ty_env_vars_reference::Args {
|
||||
mode: args.mode,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
119
crates/ruff_dev/src/generate_ty_env_vars_reference.rs
Normal file
119
crates/ruff_dev/src/generate_ty_env_vars_reference.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
//! Generate the environment variables reference from `ty_static::EnvVars`.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::bail;
|
||||
use pretty_assertions::StrComparison;
|
||||
|
||||
use ty_static::EnvVars;
|
||||
|
||||
use crate::generate_all::Mode;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[arg(long, default_value_t, value_enum)]
|
||||
pub(crate) mode: Mode,
|
||||
}
|
||||
|
||||
pub(crate) fn main(args: &Args) -> anyhow::Result<()> {
|
||||
let reference_string = generate();
|
||||
let filename = "environment.md";
|
||||
let reference_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("crates")
|
||||
.join("ty")
|
||||
.join("docs")
|
||||
.join(filename);
|
||||
|
||||
match args.mode {
|
||||
Mode::DryRun => {
|
||||
println!("{reference_string}");
|
||||
}
|
||||
Mode::Check => match fs::read_to_string(&reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
let comparison = StrComparison::new(¤t, &reference_string);
|
||||
bail!(
|
||||
"{filename} changed, please run `cargo dev generate-ty-env-vars-reference`:\n{comparison}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
bail!(
|
||||
"{filename} not found, please run `cargo dev generate-ty-env-vars-reference`"
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
bail!(
|
||||
"{filename} changed, please run `cargo dev generate-ty-env-vars-reference`:\n{err}"
|
||||
);
|
||||
}
|
||||
},
|
||||
Mode::Write => {
|
||||
// Ensure the docs directory exists
|
||||
if let Some(parent) = reference_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
match fs::read_to_string(&reference_path) {
|
||||
Ok(current) => {
|
||||
if current == reference_string {
|
||||
println!("Up-to-date: {filename}");
|
||||
} else {
|
||||
println!("Updating: {filename}");
|
||||
fs::write(&reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
println!("Updating: {filename}");
|
||||
fs::write(&reference_path, reference_string.as_bytes())?;
|
||||
}
|
||||
Err(err) => {
|
||||
bail!(
|
||||
"{filename} changed, please run `cargo dev generate-ty-env-vars-reference`:\n{err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str("# Environment variables\n\n");
|
||||
|
||||
// Partition and sort environment variables into TY_ and external variables.
|
||||
let (ty_vars, external_vars): (BTreeSet<_>, BTreeSet<_>) = EnvVars::metadata()
|
||||
.iter()
|
||||
.partition(|(var, _)| var.starts_with("TY_"));
|
||||
|
||||
output.push_str("ty defines and respects the following environment variables:\n\n");
|
||||
|
||||
for (var, doc) in ty_vars {
|
||||
output.push_str(&render(var, doc));
|
||||
}
|
||||
|
||||
output.push_str("## Externally-defined variables\n\n");
|
||||
output.push_str("ty also reads the following externally defined environment variables:\n\n");
|
||||
|
||||
for (var, doc) in external_vars {
|
||||
output.push_str(&render(var, doc));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Render an environment variable and its documentation.
|
||||
fn render(var: &str, doc: &str) -> String {
|
||||
format!("### `{var}`\n\n{doc}\n\n")
|
||||
}
|
||||
@@ -18,6 +18,7 @@ mod generate_json_schema;
|
||||
mod generate_options;
|
||||
mod generate_rules_table;
|
||||
mod generate_ty_cli_reference;
|
||||
mod generate_ty_env_vars_reference;
|
||||
mod generate_ty_options;
|
||||
mod generate_ty_rules;
|
||||
mod generate_ty_schema;
|
||||
@@ -53,6 +54,8 @@ enum Command {
|
||||
/// Generate a Markdown-compatible listing of configuration options.
|
||||
GenerateOptions,
|
||||
GenerateTyOptions(generate_ty_options::Args),
|
||||
/// Generate environment variables reference for ty.
|
||||
GenerateTyEnvVarsReference(generate_ty_env_vars_reference::Args),
|
||||
/// Generate CLI help.
|
||||
GenerateCliHelp(generate_cli_help::Args),
|
||||
/// Generate Markdown docs.
|
||||
@@ -98,6 +101,7 @@ fn main() -> Result<ExitCode> {
|
||||
Command::GenerateTyRules(args) => generate_ty_rules::main(&args)?,
|
||||
Command::GenerateOptions => println!("{}", generate_options::generate()),
|
||||
Command::GenerateTyOptions(args) => generate_ty_options::main(&args)?,
|
||||
Command::GenerateTyEnvVarsReference(args) => generate_ty_env_vars_reference::main(&args)?,
|
||||
Command::GenerateCliHelp(args) => generate_cli_help::main(&args)?,
|
||||
Command::GenerateDocs(args) => generate_docs::main(&args)?,
|
||||
Command::PrintAST(args) => print_ast::main(&args)?,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
Should emit:
|
||||
B017 - on lines 23 and 41
|
||||
B017 - on lines 24, 28, 46, 49, 52, and 58
|
||||
"""
|
||||
import asyncio
|
||||
import unittest
|
||||
import pytest
|
||||
import pytest, contextlib
|
||||
|
||||
CONSTANT = True
|
||||
|
||||
28
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B017_1.py
vendored
Normal file
28
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B017_1.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Should emit:
|
||||
B017 - on lines 20, 21, 25, and 26
|
||||
"""
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
|
||||
def something_else() -> None:
|
||||
for i in (1, 2, 3):
|
||||
print(i)
|
||||
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
|
||||
class Foobar(unittest.TestCase):
|
||||
def call_form_raises(self) -> None:
|
||||
self.assertRaises(Exception, something_else)
|
||||
self.assertRaises(BaseException, something_else)
|
||||
|
||||
|
||||
def test_pytest_call_form() -> None:
|
||||
pytest.raises(Exception, something_else)
|
||||
pytest.raises(BaseException, something_else)
|
||||
|
||||
pytest.raises(Exception, something_else, match="hello")
|
||||
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/whitespace.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/whitespace.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/19175
|
||||
# there is a (potentially invisible) unicode formfeed character (000C) between `TYPE_CHECKING` and the backslash
|
||||
from typing import TYPE_CHECKING\
|
||||
|
||||
if TYPE_CHECKING: import builtins
|
||||
builtins.print("!")
|
||||
@@ -125,3 +125,19 @@ class ClassForCommentEnthusiasts(BaseClass):
|
||||
self
|
||||
# also a comment
|
||||
).f()
|
||||
|
||||
|
||||
# Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed
|
||||
class Ord(int):
|
||||
def __len__(self):
|
||||
return super(Ord, self, uhoh=True, **{"error": True}).bit_length()
|
||||
|
||||
class ExampleWithKeywords:
|
||||
def method1(self):
|
||||
super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed
|
||||
|
||||
def method2(self):
|
||||
super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
|
||||
def method3(self):
|
||||
super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
|
||||
@@ -7,7 +7,9 @@ use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_optional_as_none_in_union_enabled;
|
||||
use crate::preview::{
|
||||
is_assert_raises_exception_call_enabled, is_optional_as_none_in_union_enabled,
|
||||
};
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
airflow, flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear,
|
||||
@@ -1037,27 +1039,14 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
|
||||
}
|
||||
if checker.any_rule_enabled(&[
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::OsReplace,
|
||||
Rule::OsRmdir,
|
||||
Rule::OsRemove,
|
||||
Rule::OsUnlink,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsPathExists,
|
||||
Rule::OsPathExpanduser,
|
||||
Rule::OsPathIsdir,
|
||||
Rule::OsPathIsfile,
|
||||
Rule::OsPathIslink,
|
||||
Rule::OsReadlink,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathIsabs,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathBasename,
|
||||
Rule::OsPathDirname,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
@@ -1068,21 +1057,66 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
]) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetsize) {
|
||||
flake8_use_pathlib::rules::os_path_getsize(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetatime) {
|
||||
flake8_use_pathlib::rules::os_path_getatime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetctime) {
|
||||
flake8_use_pathlib::rules::os_path_getctime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetmtime) {
|
||||
flake8_use_pathlib::rules::os_path_getmtime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, call);
|
||||
if let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) {
|
||||
let segments = qualified_name.segments();
|
||||
if checker.is_rule_enabled(Rule::OsPathGetsize) {
|
||||
flake8_use_pathlib::rules::os_path_getsize(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetatime) {
|
||||
flake8_use_pathlib::rules::os_path_getatime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetctime) {
|
||||
flake8_use_pathlib::rules::os_path_getctime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetmtime) {
|
||||
flake8_use_pathlib::rules::os_path_getmtime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathAbspath) {
|
||||
flake8_use_pathlib::rules::os_path_abspath(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsRmdir) {
|
||||
flake8_use_pathlib::rules::os_rmdir(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsRemove) {
|
||||
flake8_use_pathlib::rules::os_remove(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsUnlink) {
|
||||
flake8_use_pathlib::rules::os_unlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathExists) {
|
||||
flake8_use_pathlib::rules::os_path_exists(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathExpanduser) {
|
||||
flake8_use_pathlib::rules::os_path_expanduser(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathBasename) {
|
||||
flake8_use_pathlib::rules::os_path_basename(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathDirname) {
|
||||
flake8_use_pathlib::rules::os_path_dirname(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsabs) {
|
||||
flake8_use_pathlib::rules::os_path_isabs(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsdir) {
|
||||
flake8_use_pathlib::rules::os_path_isdir(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsfile) {
|
||||
flake8_use_pathlib::rules::os_path_isfile(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIslink) {
|
||||
flake8_use_pathlib::rules::os_path_islink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsReadlink) {
|
||||
flake8_use_pathlib::rules::os_readlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(
|
||||
checker, call, segments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::OsSepSplit) {
|
||||
flake8_use_pathlib::rules::os_sep_split(checker, call);
|
||||
}
|
||||
@@ -1236,6 +1270,11 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::NonOctalPermissions) {
|
||||
ruff::rules::non_octal_permissions(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::AssertRaisesException)
|
||||
&& is_assert_raises_exception_call_enabled(checker.settings())
|
||||
{
|
||||
flake8_bugbear::rules::assert_raises_exception_call(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(dict) => {
|
||||
if checker.any_rule_enabled(&[
|
||||
|
||||
@@ -64,7 +64,6 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use crate::checkers::ast::annotation::AnnotationContext;
|
||||
use crate::docstrings::extraction::ExtractionTarget;
|
||||
use crate::importer::{ImportRequest, Importer, ResolutionError};
|
||||
use crate::message::diagnostic_from_violation;
|
||||
use crate::noqa::NoqaMapping;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::preview::is_undefined_export_in_dunder_init_enabled;
|
||||
@@ -671,7 +670,12 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
| SemanticSyntaxErrorKind::InvalidStarExpression
|
||||
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateParameter(_)
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel
|
||||
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
|
||||
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_)
|
||||
| SemanticSyntaxErrorKind::InvalidNonlocal(_) => {
|
||||
self.semantic_errors.borrow_mut().push(error);
|
||||
}
|
||||
}
|
||||
@@ -3158,7 +3162,7 @@ impl<'a> LintContext<'a> {
|
||||
) -> DiagnosticGuard<'chk, 'a> {
|
||||
DiagnosticGuard {
|
||||
context: self,
|
||||
diagnostic: Some(diagnostic_from_violation(kind, range, &self.source_file)),
|
||||
diagnostic: Some(kind.into_diagnostic(range, &self.source_file)),
|
||||
rule: T::rule(),
|
||||
}
|
||||
}
|
||||
@@ -3177,7 +3181,7 @@ impl<'a> LintContext<'a> {
|
||||
if self.is_rule_enabled(rule) {
|
||||
Some(DiagnosticGuard {
|
||||
context: self,
|
||||
diagnostic: Some(diagnostic_from_violation(kind, range, &self.source_file)),
|
||||
diagnostic: Some(kind.into_diagnostic(range, &self.source_file)),
|
||||
rule,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -919,27 +919,27 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Tryceratops, "401") => (RuleGroup::Stable, rules::tryceratops::rules::VerboseLogMessage),
|
||||
|
||||
// flake8-use-pathlib
|
||||
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathAbspath),
|
||||
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
|
||||
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsChmod),
|
||||
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
|
||||
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
|
||||
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRename),
|
||||
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsReplace),
|
||||
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRmdir),
|
||||
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRemove),
|
||||
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsUnlink),
|
||||
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),
|
||||
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRemove),
|
||||
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsUnlink),
|
||||
(Flake8UsePathlib, "109") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsGetcwd),
|
||||
(Flake8UsePathlib, "110") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathExists),
|
||||
(Flake8UsePathlib, "111") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathExpanduser),
|
||||
(Flake8UsePathlib, "112") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsdir),
|
||||
(Flake8UsePathlib, "113") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsfile),
|
||||
(Flake8UsePathlib, "114") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIslink),
|
||||
(Flake8UsePathlib, "115") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsReadlink),
|
||||
(Flake8UsePathlib, "110") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExists),
|
||||
(Flake8UsePathlib, "111") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExpanduser),
|
||||
(Flake8UsePathlib, "112") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsdir),
|
||||
(Flake8UsePathlib, "113") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsfile),
|
||||
(Flake8UsePathlib, "114") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIslink),
|
||||
(Flake8UsePathlib, "115") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReadlink),
|
||||
(Flake8UsePathlib, "116") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsStat),
|
||||
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsabs),
|
||||
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsabs),
|
||||
(Flake8UsePathlib, "118") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathJoin),
|
||||
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathBasename),
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathDirname),
|
||||
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathBasename),
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathDirname),
|
||||
(Flake8UsePathlib, "121") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSamefile),
|
||||
(Flake8UsePathlib, "122") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSplitext),
|
||||
(Flake8UsePathlib, "123") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::BuiltinOpen),
|
||||
|
||||
@@ -618,8 +618,7 @@ mod tests {
|
||||
use crate::fix::edits::{
|
||||
add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon,
|
||||
};
|
||||
use crate::message::diagnostic_from_violation;
|
||||
use crate::{Edit, Fix, Locator};
|
||||
use crate::{Edit, Fix, Locator, Violation};
|
||||
|
||||
/// Parse the given source using [`Mode::Module`] and return the first statement.
|
||||
fn parse_first_stmt(source: &str) -> Result<Stmt> {
|
||||
@@ -750,8 +749,8 @@ x = 1 \
|
||||
let diag = {
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
let mut iter = edits.into_iter();
|
||||
let mut diagnostic = diagnostic_from_violation(
|
||||
MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary.
|
||||
// The choice of rule here is arbitrary.
|
||||
let mut diagnostic = MissingNewlineAtEndOfFile.into_diagnostic(
|
||||
TextRange::default(),
|
||||
&SourceFileBuilder::new("<filename>", "<code>").finish(),
|
||||
);
|
||||
|
||||
@@ -172,11 +172,10 @@ mod tests {
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::fix::{FixResult, apply_fixes};
|
||||
use crate::message::diagnostic_from_violation;
|
||||
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
|
||||
use crate::{Edit, Fix};
|
||||
use crate::{Locator, Violation};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
|
||||
fn create_diagnostics(
|
||||
@@ -187,8 +186,7 @@ mod tests {
|
||||
edit.into_iter()
|
||||
.map(|edit| {
|
||||
// The choice of rule here is arbitrary.
|
||||
let mut diagnostic = diagnostic_from_violation(
|
||||
MissingNewlineAtEndOfFile,
|
||||
let mut diagnostic = MissingNewlineAtEndOfFile.into_diagnostic(
|
||||
edit.range(),
|
||||
&SourceFileBuilder::new(filename, source).finish(),
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ use ruff_python_ast::Stmt;
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::{TokenKind, Tokens};
|
||||
use ruff_python_trivia::is_python_whitespace;
|
||||
use ruff_python_trivia::{PythonWhitespace, textwrap::indent};
|
||||
use ruff_source_file::{LineRanges, UniversalNewlineIterator};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
@@ -306,7 +307,7 @@ fn match_semicolon(s: &str) -> Option<TextSize> {
|
||||
fn match_continuation(s: &str) -> Option<TextSize> {
|
||||
for (offset, c) in s.char_indices() {
|
||||
match c {
|
||||
' ' | '\t' => continue,
|
||||
_ if is_python_whitespace(c) => continue,
|
||||
'\\' => return Some(TextSize::try_from(offset).unwrap()),
|
||||
_ => break,
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ pub fn lint_only(
|
||||
|
||||
LinterResult {
|
||||
has_valid_syntax: parsed.has_valid_syntax(),
|
||||
has_no_syntax_errors: !diagnostics.iter().any(Diagnostic::is_syntax_error),
|
||||
has_no_syntax_errors: !diagnostics.iter().any(Diagnostic::is_invalid_syntax),
|
||||
diagnostics,
|
||||
}
|
||||
}
|
||||
@@ -629,7 +629,7 @@ pub fn lint_fix<'a>(
|
||||
|
||||
if iterations == 0 {
|
||||
has_valid_syntax = parsed.has_valid_syntax();
|
||||
has_no_syntax_errors = !diagnostics.iter().any(Diagnostic::is_syntax_error);
|
||||
has_no_syntax_errors = !diagnostics.iter().any(Diagnostic::is_invalid_syntax);
|
||||
} else {
|
||||
// If the source code had no syntax errors on the first pass, but
|
||||
// does on a subsequent pass, then we've introduced a
|
||||
|
||||
@@ -24,7 +24,6 @@ pub use sarif::SarifEmitter;
|
||||
pub use text::TextEmitter;
|
||||
|
||||
use crate::Fix;
|
||||
use crate::Violation;
|
||||
use crate::registry::Rule;
|
||||
|
||||
mod azure;
|
||||
@@ -108,28 +107,6 @@ where
|
||||
diagnostic
|
||||
}
|
||||
|
||||
// TODO(brent) We temporarily allow this to avoid updating all of the call sites to add
|
||||
// references. I expect this method to go away or change significantly with the rest of the
|
||||
// diagnostic refactor, but if it still exists in this form at the end of the refactor, we
|
||||
// should just update the call sites.
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
pub fn diagnostic_from_violation<T: Violation>(
|
||||
kind: T,
|
||||
range: TextRange,
|
||||
file: &SourceFile,
|
||||
) -> Diagnostic {
|
||||
create_lint_diagnostic(
|
||||
Violation::message(&kind),
|
||||
Violation::fix_title(&kind),
|
||||
range,
|
||||
None,
|
||||
None,
|
||||
file.clone(),
|
||||
None,
|
||||
T::rule(),
|
||||
)
|
||||
}
|
||||
|
||||
struct MessageWithLocation<'a> {
|
||||
message: &'a Diagnostic,
|
||||
start_location: LineColumn,
|
||||
|
||||
@@ -1225,8 +1225,6 @@ mod tests {
|
||||
use ruff_source_file::{LineEnding, SourceFileBuilder};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::Edit;
|
||||
use crate::message::diagnostic_from_violation;
|
||||
use crate::noqa::{
|
||||
Directive, LexicalError, NoqaLexerOutput, NoqaMapping, add_noqa_inner, lex_codes,
|
||||
lex_file_exemption, lex_inline_noqa,
|
||||
@@ -1234,6 +1232,7 @@ mod tests {
|
||||
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
||||
use crate::rules::pyflakes::rules::UnusedVariable;
|
||||
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
||||
use crate::{Edit, Violation};
|
||||
use crate::{Locator, generate_noqa_edits};
|
||||
|
||||
fn assert_lexed_ranges_match_slices(
|
||||
@@ -2832,10 +2831,10 @@ mod tests {
|
||||
assert_eq!(output, format!("{contents}"));
|
||||
|
||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
||||
let messages = [diagnostic_from_violation(
|
||||
UnusedVariable {
|
||||
name: "x".to_string(),
|
||||
},
|
||||
let messages = [UnusedVariable {
|
||||
name: "x".to_string(),
|
||||
}
|
||||
.into_diagnostic(
|
||||
TextRange::new(TextSize::from(0), TextSize::from(0)),
|
||||
&source_file,
|
||||
)];
|
||||
@@ -2856,15 +2855,14 @@ mod tests {
|
||||
|
||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
||||
let messages = [
|
||||
diagnostic_from_violation(
|
||||
AmbiguousVariableName("x".to_string()),
|
||||
AmbiguousVariableName("x".to_string()).into_diagnostic(
|
||||
TextRange::new(TextSize::from(0), TextSize::from(0)),
|
||||
&source_file,
|
||||
),
|
||||
diagnostic_from_violation(
|
||||
UnusedVariable {
|
||||
name: "x".to_string(),
|
||||
},
|
||||
UnusedVariable {
|
||||
name: "x".to_string(),
|
||||
}
|
||||
.into_diagnostic(
|
||||
TextRange::new(TextSize::from(0), TextSize::from(0)),
|
||||
&source_file,
|
||||
),
|
||||
@@ -2887,15 +2885,14 @@ mod tests {
|
||||
|
||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
|
||||
let messages = [
|
||||
diagnostic_from_violation(
|
||||
AmbiguousVariableName("x".to_string()),
|
||||
AmbiguousVariableName("x".to_string()).into_diagnostic(
|
||||
TextRange::new(TextSize::from(0), TextSize::from(0)),
|
||||
&source_file,
|
||||
),
|
||||
diagnostic_from_violation(
|
||||
UnusedVariable {
|
||||
name: "x".to_string(),
|
||||
},
|
||||
UnusedVariable {
|
||||
name: "x".to_string(),
|
||||
}
|
||||
.into_diagnostic(
|
||||
TextRange::new(TextSize::from(0), TextSize::from(0)),
|
||||
&source_file,
|
||||
),
|
||||
@@ -2931,11 +2928,8 @@ print(
|
||||
"#;
|
||||
let noqa_line_for = [TextRange::new(8.into(), 68.into())].into_iter().collect();
|
||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), source).finish();
|
||||
let messages = [diagnostic_from_violation(
|
||||
PrintfStringFormatting,
|
||||
TextRange::new(12.into(), 79.into()),
|
||||
&source_file,
|
||||
)];
|
||||
let messages = [PrintfStringFormatting
|
||||
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let edits = generate_noqa_edits(
|
||||
path,
|
||||
@@ -2964,11 +2958,8 @@ foo;
|
||||
bar =
|
||||
";
|
||||
let source_file = SourceFileBuilder::new(path.to_string_lossy(), source).finish();
|
||||
let messages = [diagnostic_from_violation(
|
||||
UselessSemicolon,
|
||||
TextRange::new(4.into(), 5.into()),
|
||||
&source_file,
|
||||
)];
|
||||
let messages =
|
||||
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let edits = generate_noqa_edits(
|
||||
|
||||
@@ -69,6 +69,71 @@ pub(crate) const fn is_fix_os_path_getctime_enabled(settings: &LinterSettings) -
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_abspath_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_rmdir_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_unlink_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_remove_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_exists_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_expanduser_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_isdir_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_isfile_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_islink_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_isabs_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_readlink_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_basename_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_dirname_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/11436
|
||||
// https://github.com/astral-sh/ruff/pull/11168
|
||||
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {
|
||||
@@ -125,3 +190,8 @@ pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19063
|
||||
pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::SourceFile;
|
||||
|
||||
use crate::IOError;
|
||||
use crate::message::diagnostic_from_violation;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::ruff::rules::InvalidPyprojectToml;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{IOError, Violation};
|
||||
|
||||
/// RUF200
|
||||
pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings) -> Vec<Diagnostic> {
|
||||
@@ -30,11 +29,8 @@ pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings)
|
||||
source_file.name(),
|
||||
);
|
||||
if settings.rules.enabled(Rule::IOError) {
|
||||
let diagnostic = diagnostic_from_violation(
|
||||
IOError { message },
|
||||
TextRange::default(),
|
||||
source_file,
|
||||
);
|
||||
let diagnostic =
|
||||
IOError { message }.into_diagnostic(TextRange::default(), source_file);
|
||||
messages.push(diagnostic);
|
||||
} else {
|
||||
warn!(
|
||||
@@ -56,11 +52,8 @@ pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings)
|
||||
|
||||
if settings.rules.enabled(Rule::InvalidPyprojectToml) {
|
||||
let toml_err = err.message().to_string();
|
||||
let diagnostic = diagnostic_from_violation(
|
||||
InvalidPyprojectToml { message: toml_err },
|
||||
range,
|
||||
source_file,
|
||||
);
|
||||
let diagnostic =
|
||||
InvalidPyprojectToml { message: toml_err }.into_diagnostic(range, source_file);
|
||||
messages.push(diagnostic);
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ impl Violation for SuspiciousXmlrpcImport {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import wsgiref.handlers.CGIHandler
|
||||
/// from wsgiref.handlers import CGIHandler
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
|
||||
@@ -16,11 +16,14 @@ mod tests {
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
use crate::settings::types::PreviewMode;
|
||||
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
#[test_case(Rule::AbstractBaseClassWithoutAbstractMethod, Path::new("B024.py"))]
|
||||
#[test_case(Rule::AssertFalse, Path::new("B011.py"))]
|
||||
#[test_case(Rule::AssertRaisesException, Path::new("B017.py"))]
|
||||
#[test_case(Rule::AssertRaisesException, Path::new("B017_0.py"))]
|
||||
#[test_case(Rule::AssertRaisesException, Path::new("B017_1.py"))]
|
||||
#[test_case(Rule::AssignmentToOsEnviron, Path::new("B003.py"))]
|
||||
#[test_case(Rule::CachedInstanceMethod, Path::new("B019.py"))]
|
||||
#[test_case(Rule::ClassAsDataStructure, Path::new("class_as_data_structure.py"))]
|
||||
@@ -174,4 +177,23 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::AssertRaisesException, Path::new("B017_0.py"))]
|
||||
#[test_case(Rule::AssertRaisesException, Path::new("B017_1.py"))]
|
||||
fn rules_preview(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_bugbear").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Expr, WithItem};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, WithItem};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
@@ -56,6 +56,48 @@ impl fmt::Display for ExceptionKind {
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_blind_exception(
|
||||
semantic: &ruff_python_semantic::SemanticModel<'_>,
|
||||
func: &Expr,
|
||||
arguments: &Arguments,
|
||||
) -> Option<ExceptionKind> {
|
||||
let is_assert_raises = matches!(
|
||||
func,
|
||||
&Expr::Attribute(ast::ExprAttribute { ref attr, .. }) if attr.as_str() == "assertRaises"
|
||||
);
|
||||
|
||||
let is_pytest_raises = semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pytest", "raises"]));
|
||||
|
||||
if !(is_assert_raises || is_pytest_raises) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_pytest_raises {
|
||||
if arguments.find_keyword("match").is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if arguments
|
||||
.find_positional(1)
|
||||
.is_some_and(|arg| matches!(arg, Expr::StringLiteral(_) | Expr::BytesLiteral(_)))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let first_arg = arguments.args.first()?;
|
||||
|
||||
let builtin_symbol = semantic.resolve_builtin_symbol(first_arg)?;
|
||||
|
||||
match builtin_symbol {
|
||||
"Exception" => Some(ExceptionKind::Exception),
|
||||
"BaseException" => Some(ExceptionKind::BaseException),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// B017
|
||||
pub(crate) fn assert_raises_exception(checker: &Checker, items: &[WithItem]) {
|
||||
for item in items {
|
||||
@@ -73,33 +115,31 @@ pub(crate) fn assert_raises_exception(checker: &Checker, items: &[WithItem]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let [arg] = &*arguments.args else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let semantic = checker.semantic();
|
||||
|
||||
let Some(builtin_symbol) = semantic.resolve_builtin_symbol(arg) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let exception = match builtin_symbol {
|
||||
"Exception" => ExceptionKind::Exception,
|
||||
"BaseException" => ExceptionKind::BaseException,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if !(matches!(func.as_ref(), Expr::Attribute(ast::ExprAttribute { attr, .. }) if attr == "assertRaises")
|
||||
|| semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["pytest", "raises"])
|
||||
})
|
||||
&& arguments.find_keyword("match").is_none())
|
||||
if let Some(exception) =
|
||||
detect_blind_exception(checker.semantic(), func.as_ref(), arguments)
|
||||
{
|
||||
continue;
|
||||
checker.report_diagnostic(AssertRaisesException { exception }, item.range());
|
||||
}
|
||||
|
||||
checker.report_diagnostic(AssertRaisesException { exception }, item.range());
|
||||
}
|
||||
}
|
||||
|
||||
/// B017 (call form)
|
||||
pub(crate) fn assert_raises_exception_call(
|
||||
checker: &Checker,
|
||||
ast::ExprCall {
|
||||
func,
|
||||
arguments,
|
||||
range,
|
||||
node_index: _,
|
||||
}: &ast::ExprCall,
|
||||
) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
if arguments.args.len() < 2 && arguments.find_argument("func", 1).is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(exception) = detect_blind_exception(semantic, func.as_ref(), arguments) {
|
||||
checker.report_diagnostic(AssertRaisesException { exception }, *range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B017.py:23:14: B017 Do not assert blind exception: `Exception`
|
||||
B017_0.py:23:14: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
21 | class Foobar(unittest.TestCase):
|
||||
22 | def evil_raises(self) -> None:
|
||||
@@ -10,7 +10,7 @@ B017.py:23:14: B017 Do not assert blind exception: `Exception`
|
||||
24 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017.py:27:14: B017 Do not assert blind exception: `BaseException`
|
||||
B017_0.py:27:14: B017 Do not assert blind exception: `BaseException`
|
||||
|
|
||||
26 | def also_evil_raises(self) -> None:
|
||||
27 | with self.assertRaises(BaseException):
|
||||
@@ -18,7 +18,7 @@ B017.py:27:14: B017 Do not assert blind exception: `BaseException`
|
||||
28 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017.py:45:10: B017 Do not assert blind exception: `Exception`
|
||||
B017_0.py:45:10: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
44 | def test_pytest_raises():
|
||||
45 | with pytest.raises(Exception):
|
||||
@@ -26,7 +26,7 @@ B017.py:45:10: B017 Do not assert blind exception: `Exception`
|
||||
46 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
B017.py:48:10: B017 Do not assert blind exception: `Exception`
|
||||
B017_0.py:48:10: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
46 | raise ValueError("Hello")
|
||||
47 |
|
||||
@@ -35,7 +35,7 @@ B017.py:48:10: B017 Do not assert blind exception: `Exception`
|
||||
49 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
B017.py:57:36: B017 Do not assert blind exception: `Exception`
|
||||
B017_0.py:57:36: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
55 | raise ValueError("This is also fine")
|
||||
56 |
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B017_0.py:23:14: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
21 | class Foobar(unittest.TestCase):
|
||||
22 | def evil_raises(self) -> None:
|
||||
23 | with self.assertRaises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
24 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017_0.py:27:14: B017 Do not assert blind exception: `BaseException`
|
||||
|
|
||||
26 | def also_evil_raises(self) -> None:
|
||||
27 | with self.assertRaises(BaseException):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
28 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017_0.py:45:10: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
44 | def test_pytest_raises():
|
||||
45 | with pytest.raises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
46 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
B017_0.py:48:10: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
46 | raise ValueError("Hello")
|
||||
47 |
|
||||
48 | with pytest.raises(Exception), pytest.raises(ValueError):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
49 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
B017_0.py:57:36: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
55 | raise ValueError("This is also fine")
|
||||
56 |
|
||||
57 | with contextlib.nullcontext(), pytest.raises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
58 | raise ValueError("Multiple context managers")
|
||||
|
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B017_1.py:20:9: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
18 | class Foobar(unittest.TestCase):
|
||||
19 | def call_form_raises(self) -> None:
|
||||
20 | self.assertRaises(Exception, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
21 | self.assertRaises(BaseException, something_else)
|
||||
|
|
||||
|
||||
B017_1.py:21:9: B017 Do not assert blind exception: `BaseException`
|
||||
|
|
||||
19 | def call_form_raises(self) -> None:
|
||||
20 | self.assertRaises(Exception, something_else)
|
||||
21 | self.assertRaises(BaseException, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
|
|
||||
|
||||
B017_1.py:25:5: B017 Do not assert blind exception: `Exception`
|
||||
|
|
||||
24 | def test_pytest_call_form() -> None:
|
||||
25 | pytest.raises(Exception, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
26 | pytest.raises(BaseException, something_else)
|
||||
|
|
||||
|
||||
B017_1.py:26:5: B017 Do not assert blind exception: `BaseException`
|
||||
|
|
||||
24 | def test_pytest_call_form() -> None:
|
||||
25 | pytest.raises(Exception, something_else)
|
||||
26 | pytest.raises(BaseException, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
27 |
|
||||
28 | pytest.raises(Exception, something_else, match="hello")
|
||||
|
|
||||
@@ -36,6 +36,7 @@ mod tests {
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TC004_8.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TC004_9.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("quote.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("whitespace.py"))]
|
||||
#[test_case(Rule::RuntimeStringUnion, Path::new("TC010_1.py"))]
|
||||
#[test_case(Rule::RuntimeStringUnion, Path::new("TC010_2.py"))]
|
||||
#[test_case(Rule::TypingOnlyFirstPartyImport, Path::new("TC001.py"))]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
whitespace.py:5:26: TC004 [*] Move import `builtins` out of type-checking block. Import is used for more than type hinting.
|
||||
|
|
||||
3 | from typing import TYPE_CHECKING\
|
||||
4 |
|
||||
5 | if TYPE_CHECKING: import builtins
|
||||
| ^^^^^^^^ TC004
|
||||
6 | builtins.print("!")
|
||||
|
|
||||
= help: Move out of type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | # Regression test for: https://github.com/astral-sh/ruff/issues/19175
|
||||
2 2 | # there is a (potentially invisible) unicode formfeed character (000C) between `TYPE_CHECKING` and the backslash
|
||||
3 |-from typing import TYPE_CHECKING\
|
||||
3 |+from typing import TYPE_CHECKING; import builtins\
|
||||
4 4 |
|
||||
5 |-if TYPE_CHECKING: import builtins
|
||||
5 |+if TYPE_CHECKING: pass
|
||||
6 6 | builtins.print("!")
|
||||
@@ -1,10 +1,17 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::{Applicability, Edit, Fix, Violation};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
pub(crate) fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
pub(crate) fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool {
|
||||
arguments
|
||||
.find_keyword(name)
|
||||
.is_some_and(|keyword| !keyword.value.is_none_literal_expr())
|
||||
}
|
||||
|
||||
pub(crate) fn is_pathlib_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
expr.as_call_expr().is_some_and(|expr_call| {
|
||||
checker
|
||||
.semantic()
|
||||
@@ -13,27 +20,22 @@ pub(crate) fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn check_os_path_get_calls(
|
||||
/// We check functions that take only 1 argument, this does not apply to functions
|
||||
/// with `dir_fd` argument, because `dir_fd` is not supported by pathlib,
|
||||
/// so check if it's set to non-default values
|
||||
pub(crate) fn check_os_pathlib_single_arg_calls(
|
||||
checker: &Checker,
|
||||
call: &ExprCall,
|
||||
fn_name: &str,
|
||||
attr: &str,
|
||||
fn_argument: &str,
|
||||
fix_enabled: bool,
|
||||
violation: impl Violation,
|
||||
) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(&call.func)
|
||||
.is_none_or(|qualified_name| qualified_name.segments() != ["os", "path", fn_name])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument_value("filename", 0) else {
|
||||
let Some(arg) = call.arguments.find_argument_value(fn_argument, 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -56,10 +58,10 @@ pub(crate) fn check_os_path_get_calls(
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
let replacement = if is_path_call(checker, arg) {
|
||||
format!("{arg_code}.stat().{attr}")
|
||||
let replacement = if is_pathlib_path_call(checker, arg) {
|
||||
format!("{arg_code}.{attr}")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).stat().{attr}")
|
||||
format!("{binding}({arg_code}).{attr}")
|
||||
};
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
|
||||
@@ -80,6 +80,48 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("full_name.py"))]
|
||||
#[test_case(Path::new("import_as.py"))]
|
||||
#[test_case(Path::new("import_from_as.py"))]
|
||||
#[test_case(Path::new("import_from.py"))]
|
||||
fn preview_rules(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview_{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_use_pathlib").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(vec![
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::OsReplace,
|
||||
Rule::OsRmdir,
|
||||
Rule::OsRemove,
|
||||
Rule::OsUnlink,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsPathExists,
|
||||
Rule::OsPathExpanduser,
|
||||
Rule::OsPathIsdir,
|
||||
Rule::OsPathIsfile,
|
||||
Rule::OsPathIslink,
|
||||
Rule::OsReadlink,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathIsabs,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathBasename,
|
||||
Rule::OsPathDirname,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202.py"))]
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202_2.py"))]
|
||||
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
pub(crate) use glob_rule::*;
|
||||
pub(crate) use invalid_pathlib_with_suffix::*;
|
||||
pub(crate) use os_path_abspath::*;
|
||||
pub(crate) use os_path_basename::*;
|
||||
pub(crate) use os_path_dirname::*;
|
||||
pub(crate) use os_path_exists::*;
|
||||
pub(crate) use os_path_expanduser::*;
|
||||
pub(crate) use os_path_getatime::*;
|
||||
pub(crate) use os_path_getctime::*;
|
||||
pub(crate) use os_path_getmtime::*;
|
||||
pub(crate) use os_path_getsize::*;
|
||||
pub(crate) use os_path_isabs::*;
|
||||
pub(crate) use os_path_isdir::*;
|
||||
pub(crate) use os_path_isfile::*;
|
||||
pub(crate) use os_path_islink::*;
|
||||
pub(crate) use os_readlink::*;
|
||||
pub(crate) use os_remove::*;
|
||||
pub(crate) use os_rmdir::*;
|
||||
pub(crate) use os_sep_split::*;
|
||||
pub(crate) use os_unlink::*;
|
||||
pub(crate) use path_constructor_current_directory::*;
|
||||
pub(crate) use replaceable_by_pathlib::*;
|
||||
|
||||
mod glob_rule;
|
||||
mod invalid_pathlib_with_suffix;
|
||||
mod os_path_abspath;
|
||||
mod os_path_basename;
|
||||
mod os_path_dirname;
|
||||
mod os_path_exists;
|
||||
mod os_path_expanduser;
|
||||
mod os_path_getatime;
|
||||
mod os_path_getctime;
|
||||
mod os_path_getmtime;
|
||||
mod os_path_getsize;
|
||||
mod os_path_isabs;
|
||||
mod os_path_isdir;
|
||||
mod os_path_isfile;
|
||||
mod os_path_islink;
|
||||
mod os_readlink;
|
||||
mod os_remove;
|
||||
mod os_rmdir;
|
||||
mod os_sep_split;
|
||||
mod os_unlink;
|
||||
mod path_constructor_current_directory;
|
||||
mod replaceable_by_pathlib;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_abspath_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.abspath`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.resolve()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.abspath()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// file_path = os.path.abspath("../path/to/file")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// file_path = Path("../path/to/file").resolve()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
|
||||
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathAbspath;
|
||||
|
||||
impl Violation for OsPathAbspath {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.abspath()` should be replaced by `Path.resolve()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).resolve()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH100
|
||||
pub(crate) fn os_path_abspath(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "abspath"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"resolve()",
|
||||
"path",
|
||||
is_fix_os_path_abspath_enabled(checker.settings()),
|
||||
OsPathAbspath,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_basename_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.basename`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.name` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.basename()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.basename(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).name
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathBasename;
|
||||
|
||||
impl Violation for OsPathBasename {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.basename()` should be replaced by `Path.name`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).name`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH119
|
||||
pub(crate) fn os_path_basename(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "basename"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"name",
|
||||
"p",
|
||||
is_fix_os_path_basename_enabled(checker.settings()),
|
||||
OsPathBasename,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_dirname_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.dirname`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.parent` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.dirname()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.dirname(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).parent
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
|
||||
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathDirname;
|
||||
|
||||
impl Violation for OsPathDirname {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.dirname()` should be replaced by `Path.parent`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).parent`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH120
|
||||
pub(crate) fn os_path_dirname(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "dirname"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"parent",
|
||||
"p",
|
||||
is_fix_os_path_dirname_enabled(checker.settings()),
|
||||
OsPathDirname,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_exists_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.exists`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.exists()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.exists()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.exists("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").exists()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
|
||||
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExists;
|
||||
|
||||
impl Violation for OsPathExists {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.exists()` should be replaced by `Path.exists()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).exists()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH110
|
||||
pub(crate) fn os_path_exists(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "exists"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"exists()",
|
||||
"path",
|
||||
is_fix_os_path_exists_enabled(checker.settings()),
|
||||
OsPathExists,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_expanduser_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.expanduser`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.expanduser()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.expanduser()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.expanduser("~/films/Monty Python")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("~/films/Monty Python").expanduser()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExpanduser;
|
||||
|
||||
impl Violation for OsPathExpanduser {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.expanduser()` should be replaced by `Path.expanduser()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).expanduser()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH111
|
||||
pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "expanduser"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"expanduser()",
|
||||
"path",
|
||||
is_fix_os_path_expanduser_enabled(checker.settings()),
|
||||
OsPathExpanduser,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getatime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -61,12 +61,15 @@ impl Violation for OsPathGetatime {
|
||||
}
|
||||
|
||||
/// PTH203
|
||||
pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getatime"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getatime",
|
||||
"st_atime",
|
||||
"stat().st_atime",
|
||||
"filename",
|
||||
is_fix_os_path_getatime_enabled(checker.settings()),
|
||||
OsPathGetatime,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getctime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -62,12 +62,15 @@ impl Violation for OsPathGetctime {
|
||||
}
|
||||
|
||||
/// PTH205
|
||||
pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getctime"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getctime",
|
||||
"st_ctime",
|
||||
"stat().st_ctime",
|
||||
"filename",
|
||||
is_fix_os_path_getctime_enabled(checker.settings()),
|
||||
OsPathGetctime,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getmtime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -62,12 +62,15 @@ impl Violation for OsPathGetmtime {
|
||||
}
|
||||
|
||||
/// PTH204
|
||||
pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getmtime"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getmtime",
|
||||
"st_mtime",
|
||||
"stat().st_mtime",
|
||||
"filename",
|
||||
is_fix_os_path_getmtime_enabled(checker.settings()),
|
||||
OsPathGetmtime,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getsize_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -62,12 +62,15 @@ impl Violation for OsPathGetsize {
|
||||
}
|
||||
|
||||
/// PTH202
|
||||
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getsize"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getsize",
|
||||
"st_size",
|
||||
"stat().st_size",
|
||||
"filename",
|
||||
is_fix_os_path_getsize_enabled(checker.settings()),
|
||||
OsPathGetsize,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isabs_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isabs`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_absolute()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.isabs()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// if os.path.isabs(file_name):
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// if Path(file_name).is_absolute():
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
|
||||
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsabs;
|
||||
|
||||
impl Violation for OsPathIsabs {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isabs()` should be replaced by `Path.is_absolute()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_absolute()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH117
|
||||
pub(crate) fn os_path_isabs(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "isabs"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_absolute()",
|
||||
"s",
|
||||
is_fix_os_path_isabs_enabled(checker.settings()),
|
||||
OsPathIsabs,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isdir_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_dir()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isdir("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_dir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
|
||||
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsdir;
|
||||
|
||||
impl Violation for OsPathIsdir {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isdir()` should be replaced by `Path.is_dir()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_dir()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH112
|
||||
pub(crate) fn os_path_isdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "isdir"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_dir()",
|
||||
"s",
|
||||
is_fix_os_path_isdir_enabled(checker.settings()),
|
||||
OsPathIsdir,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isfile_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isfile`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_file()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isfile()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isfile("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_file()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
|
||||
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsfile;
|
||||
|
||||
impl Violation for OsPathIsfile {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isfile()` should be replaced by `Path.is_file()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_file()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH113
|
||||
pub(crate) fn os_path_isfile(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "isfile"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_file()",
|
||||
"path",
|
||||
is_fix_os_path_isfile_enabled(checker.settings()),
|
||||
OsPathIsfile,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_islink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.islink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_symlink()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.islink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.islink("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_symlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIslink;
|
||||
|
||||
impl Violation for OsPathIslink {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.islink()` should be replaced by `Path.is_symlink()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_symlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH114
|
||||
pub(crate) fn os_path_islink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "islink"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_symlink()",
|
||||
"path",
|
||||
is_fix_os_path_islink_enabled(checker.settings()),
|
||||
OsPathIslink,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_readlink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{ExprCall, PythonVersion};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.readlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.readlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.readlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.readlink(file_name)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(file_name).readlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
|
||||
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsReadlink;
|
||||
|
||||
impl Violation for OsReadlink {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.readlink()` should be replaced by `Path.readlink()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).readlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH115
|
||||
pub(crate) fn os_readlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// Python 3.9+
|
||||
if checker.target_version() < PythonVersion::PY39 {
|
||||
return;
|
||||
}
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.readlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.readlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "readlink"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"readlink()",
|
||||
"path",
|
||||
is_fix_os_readlink_enabled(checker.settings()),
|
||||
OsReadlink,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_remove_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.remove`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.remove()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.remove("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRemove;
|
||||
|
||||
impl Violation for OsRemove {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.remove()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).unlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH107
|
||||
pub(crate) fn os_remove(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.remove)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.remove(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "remove"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"unlink()",
|
||||
"path",
|
||||
is_fix_os_remove_enabled(checker.settings()),
|
||||
OsRemove,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_rmdir_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.rmdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.rmdir()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.rmdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.rmdir("folder/")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("folder/").rmdir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
|
||||
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRmdir;
|
||||
|
||||
impl Violation for OsRmdir {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.rmdir()` should be replaced by `Path.rmdir()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).rmdir()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH106
|
||||
pub(crate) fn os_rmdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.rmdir(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "rmdir"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"rmdir()",
|
||||
"path",
|
||||
is_fix_os_rmdir_enabled(checker.settings()),
|
||||
OsRmdir,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_unlink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.unlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.unlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.unlink("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsUnlink;
|
||||
|
||||
impl Violation for OsUnlink {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.unlink()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).unlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH108
|
||||
pub(crate) fn os_unlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.unlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.unlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "unlink"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"unlink()",
|
||||
"path",
|
||||
is_fix_os_unlink_enabled(checker.settings()),
|
||||
OsUnlink,
|
||||
);
|
||||
}
|
||||
@@ -54,7 +54,11 @@ impl AlwaysFixableViolation for PathConstructorCurrentDirectory {
|
||||
}
|
||||
|
||||
/// PTH201
|
||||
pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprCall) {
|
||||
pub(crate) fn path_constructor_current_directory(
|
||||
checker: &Checker,
|
||||
call: &ExprCall,
|
||||
segments: &[&str],
|
||||
) {
|
||||
let applicability = |range| {
|
||||
if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
@@ -63,15 +67,9 @@ pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprC
|
||||
}
|
||||
};
|
||||
|
||||
let (func, arguments) = (&call.func, &call.arguments);
|
||||
let arguments = &call.arguments;
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["pathlib", "Path" | "PurePath"])
|
||||
})
|
||||
{
|
||||
if !matches!(segments, ["pathlib", "Path" | "PurePath"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@ use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_use_pathlib::helpers::is_keyword_only_argument_non_default;
|
||||
use crate::rules::flake8_use_pathlib::rules::Glob;
|
||||
use crate::rules::flake8_use_pathlib::violations::{
|
||||
BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathAbspath,
|
||||
OsPathBasename, OsPathDirname, OsPathExists, OsPathExpanduser, OsPathIsabs, OsPathIsdir,
|
||||
OsPathIsfile, OsPathIslink, OsPathJoin, OsPathSamefile, OsPathSplitext, OsReadlink, OsRemove,
|
||||
OsRename, OsReplace, OsRmdir, OsStat, OsSymlink, OsUnlink, PyPath,
|
||||
BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathJoin,
|
||||
OsPathSamefile, OsPathSplitext, OsRename, OsReplace, OsStat, OsSymlink, PyPath,
|
||||
};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) else {
|
||||
@@ -20,8 +18,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
|
||||
let range = call.func.range();
|
||||
match qualified_name.segments() {
|
||||
// PTH100
|
||||
["os", "path", "abspath"] => checker.report_diagnostic_if_enabled(OsPathAbspath, range),
|
||||
// PTH101
|
||||
["os", "chmod"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
@@ -87,60 +83,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsReplace, range)
|
||||
}
|
||||
// PTH106
|
||||
["os", "rmdir"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.rmdir(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsRmdir, range)
|
||||
}
|
||||
// PTH107
|
||||
["os", "remove"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.remove)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.remove(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsRemove, range)
|
||||
}
|
||||
// PTH108
|
||||
["os", "unlink"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.unlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.unlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsUnlink, range)
|
||||
}
|
||||
// PTH109
|
||||
["os", "getcwd"] => checker.report_diagnostic_if_enabled(OsGetcwd, range),
|
||||
["os", "getcwdb"] => checker.report_diagnostic_if_enabled(OsGetcwd, range),
|
||||
// PTH110
|
||||
["os", "path", "exists"] => checker.report_diagnostic_if_enabled(OsPathExists, range),
|
||||
// PTH111
|
||||
["os", "path", "expanduser"] => {
|
||||
checker.report_diagnostic_if_enabled(OsPathExpanduser, range)
|
||||
}
|
||||
// PTH112
|
||||
["os", "path", "isdir"] => checker.report_diagnostic_if_enabled(OsPathIsdir, range),
|
||||
// PTH113
|
||||
["os", "path", "isfile"] => checker.report_diagnostic_if_enabled(OsPathIsfile, range),
|
||||
// PTH114
|
||||
["os", "path", "islink"] => checker.report_diagnostic_if_enabled(OsPathIslink, range),
|
||||
|
||||
// PTH116
|
||||
["os", "stat"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
@@ -159,8 +105,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsStat, range)
|
||||
}
|
||||
// PTH117
|
||||
["os", "path", "isabs"] => checker.report_diagnostic_if_enabled(OsPathIsabs, range),
|
||||
// PTH118
|
||||
["os", "path", "join"] => checker.report_diagnostic_if_enabled(
|
||||
OsPathJoin {
|
||||
@@ -184,10 +128,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
},
|
||||
range,
|
||||
),
|
||||
// PTH119
|
||||
["os", "path", "basename"] => checker.report_diagnostic_if_enabled(OsPathBasename, range),
|
||||
// PTH120
|
||||
["os", "path", "dirname"] => checker.report_diagnostic_if_enabled(OsPathDirname, range),
|
||||
// PTH121
|
||||
["os", "path", "samefile"] => checker.report_diagnostic_if_enabled(OsPathSamefile, range),
|
||||
// PTH122
|
||||
@@ -208,7 +148,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
|
||||
// PTH123
|
||||
["" | "builtins", "open"] => {
|
||||
// `closefd` and `opener` are not supported by pathlib, so check if they are
|
||||
// `closefd` and `opener` are not supported by pathlib, so check if they
|
||||
// are set to non-default values.
|
||||
// https://github.com/astral-sh/ruff/issues/7620
|
||||
// Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open):
|
||||
@@ -282,20 +222,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
range,
|
||||
)
|
||||
}
|
||||
// PTH115
|
||||
// Python 3.9+
|
||||
["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.readlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.readlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsReadlink, range)
|
||||
}
|
||||
// PTH208
|
||||
["os", "listdir"] => {
|
||||
if call
|
||||
@@ -338,7 +264,7 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
|
||||
match expr {
|
||||
Expr::Name(name) => Some(name),
|
||||
Expr::Call(ast::ExprCall { func, .. }) => get_name_expr(func),
|
||||
Expr::Call(ExprCall { func, .. }) => get_name_expr(func),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -349,9 +275,3 @@ fn is_argument_non_default(arguments: &ast::Arguments, name: &str, position: usi
|
||||
.find_argument_value(name, position)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
}
|
||||
|
||||
fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool {
|
||||
arguments
|
||||
.find_keyword(name)
|
||||
.is_some_and(|keyword| !keyword.value.is_none_literal_expr())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ full_name.py:7:5: PTH100 `os.path.abspath()` should be replaced by `Path.resolve
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
full_name.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ full_name.py:13:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
full_name.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ full_name.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
full_name.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ full_name.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
full_name.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ full_name.py:17:5: PTH110 `os.path.exists()` should be replaced by `Path.exists(
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
full_name.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ full_name.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.exp
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
full_name.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ full_name.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
full_name.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ full_name.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
full_name.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ full_name.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_syml
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
full_name.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ full_name.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
full_name.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ full_name.py:24:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_absol
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
full_name.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ full_name.py:28:1: PTH119 `os.path.basename()` should be replaced by `Path.name`
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
full_name.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ full_name.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
full_name.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -10,6 +10,7 @@ import_as.py:7:5: PTH100 `os.path.abspath()` should be replaced by `Path.resolve
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
import_as.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ import_as.py:13:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
import_as.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ import_as.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_as.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ import_as.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_as.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ import_as.py:17:5: PTH110 `os.path.exists()` should be replaced by `Path.exists(
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
import_as.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ import_as.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.exp
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
import_as.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ import_as.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
import_as.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ import_as.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
import_as.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ import_as.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_syml
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
import_as.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ import_as.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
import_as.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ import_as.py:24:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_absol
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
import_as.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ import_as.py:28:1: PTH119 `os.path.basename()` should be replaced by `Path.name`
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
import_as.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ import_as.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent
|
||||
30 | foo_p.samefile(p)
|
||||
31 | foo_p.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
import_as.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -10,6 +10,7 @@ import_from.py:9:5: PTH100 `os.path.abspath()` should be replaced by `Path.resol
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
import_from.py:10:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ import_from.py:15:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
import_from.py:16:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ import_from.py:16:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from.py:17:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ import_from.py:17:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from.py:18:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ import_from.py:19:5: PTH110 `os.path.exists()` should be replaced by `Path.exist
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
import_from.py:20:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ import_from.py:20:6: PTH111 `os.path.expanduser()` should be replaced by `Path.e
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
import_from.py:21:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ import_from.py:21:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
import_from.py:22:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ import_from.py:22:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_fi
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
import_from.py:23:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ import_from.py:23:9: PTH114 `os.path.islink()` should be replaced by `Path.is_sy
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
import_from.py:24:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ import_from.py:24:1: PTH115 `os.readlink()` should be replaced by `Path.readlink
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
import_from.py:25:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ import_from.py:26:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_abs
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
import_from.py:27:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ import_from.py:30:1: PTH119 `os.path.basename()` should be replaced by `Path.nam
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
import_from.py:31:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ import_from.py:31:1: PTH120 `os.path.dirname()` should be replaced by `Path.pare
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
import_from.py:32:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -10,6 +10,7 @@ import_from_as.py:14:5: PTH100 `os.path.abspath()` should be replaced by `Path.r
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
import_from_as.py:15:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ import_from_as.py:20:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
import_from_as.py:21:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ import_from_as.py:21:1: PTH107 `os.remove()` should be replaced by `Path.unlink(
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from_as.py:22:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ import_from_as.py:22:1: PTH108 `os.unlink()` should be replaced by `Path.unlink(
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from_as.py:23:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ import_from_as.py:24:5: PTH110 `os.path.exists()` should be replaced by `Path.ex
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
import_from_as.py:25:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ import_from_as.py:25:6: PTH111 `os.path.expanduser()` should be replaced by `Pat
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
import_from_as.py:26:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ import_from_as.py:26:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
import_from_as.py:27:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ import_from_as.py:27:8: PTH113 `os.path.isfile()` should be replaced by `Path.is
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
import_from_as.py:28:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ import_from_as.py:28:9: PTH114 `os.path.islink()` should be replaced by `Path.is
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
import_from_as.py:29:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ import_from_as.py:29:1: PTH115 `os.readlink()` should be replaced by `Path.readl
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
import_from_as.py:30:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ import_from_as.py:31:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
import_from_as.py:32:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ import_from_as.py:35:1: PTH119 `os.path.basename()` should be replaced by `Path.
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
import_from_as.py:36:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ import_from_as.py:36:1: PTH120 `os.path.dirname()` should be replaced by `Path.p
|
||||
37 | xsamefile(p)
|
||||
38 | xsplitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
import_from_as.py:37:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -0,0 +1,580 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
full_name.py:7:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
5 | q = "bar"
|
||||
6 |
|
||||
7 | a = os.path.abspath(p)
|
||||
| ^^^^^^^^^^^^^^^ PTH100
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
6 7 |
|
||||
7 |-a = os.path.abspath(p)
|
||||
8 |+a = pathlib.Path(p).resolve()
|
||||
8 9 | aa = os.chmod(p)
|
||||
9 10 | aaa = os.mkdir(p)
|
||||
10 11 | os.makedirs(p)
|
||||
|
||||
full_name.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
7 | a = os.path.abspath(p)
|
||||
8 | aa = os.chmod(p)
|
||||
| ^^^^^^^^ PTH101
|
||||
9 | aaa = os.mkdir(p)
|
||||
10 | os.makedirs(p)
|
||||
|
|
||||
|
||||
full_name.py:9:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
7 | a = os.path.abspath(p)
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
| ^^^^^^^^ PTH102
|
||||
10 | os.makedirs(p)
|
||||
11 | os.rename(p)
|
||||
|
|
||||
|
||||
full_name.py:10:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
10 | os.makedirs(p)
|
||||
| ^^^^^^^^^^^ PTH103
|
||||
11 | os.rename(p)
|
||||
12 | os.replace(p)
|
||||
|
|
||||
|
||||
full_name.py:11:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
9 | aaa = os.mkdir(p)
|
||||
10 | os.makedirs(p)
|
||||
11 | os.rename(p)
|
||||
| ^^^^^^^^^ PTH104
|
||||
12 | os.replace(p)
|
||||
13 | os.rmdir(p)
|
||||
|
|
||||
|
||||
full_name.py:12:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
10 | os.makedirs(p)
|
||||
11 | os.rename(p)
|
||||
12 | os.replace(p)
|
||||
| ^^^^^^^^^^ PTH105
|
||||
13 | os.rmdir(p)
|
||||
14 | os.remove(p)
|
||||
|
|
||||
|
||||
full_name.py:13:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
11 | os.rename(p)
|
||||
12 | os.replace(p)
|
||||
13 | os.rmdir(p)
|
||||
| ^^^^^^^^ PTH106
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
10 11 | os.makedirs(p)
|
||||
11 12 | os.rename(p)
|
||||
12 13 | os.replace(p)
|
||||
13 |-os.rmdir(p)
|
||||
14 |+pathlib.Path(p).rmdir()
|
||||
14 15 | os.remove(p)
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
|
||||
full_name.py:14:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
12 | os.replace(p)
|
||||
13 | os.rmdir(p)
|
||||
14 | os.remove(p)
|
||||
| ^^^^^^^^^ PTH107
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
11 12 | os.rename(p)
|
||||
12 13 | os.replace(p)
|
||||
13 14 | os.rmdir(p)
|
||||
14 |-os.remove(p)
|
||||
15 |+pathlib.Path(p).unlink()
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
|
||||
full_name.py:15:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
13 | os.rmdir(p)
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
| ^^^^^^^^^ PTH108
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
12 13 | os.replace(p)
|
||||
13 14 | os.rmdir(p)
|
||||
14 15 | os.remove(p)
|
||||
15 |-os.unlink(p)
|
||||
16 |+pathlib.Path(p).unlink()
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
|
||||
full_name.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
| ^^^^^^^^^ PTH109
|
||||
17 | b = os.path.exists(p)
|
||||
18 | bb = os.path.expanduser(p)
|
||||
|
|
||||
|
||||
full_name.py:17:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
| ^^^^^^^^^^^^^^ PTH110
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
14 15 | os.remove(p)
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
17 |-b = os.path.exists(p)
|
||||
18 |+b = pathlib.Path(p).exists()
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
|
||||
full_name.py:18:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
18 | bb = os.path.expanduser(p)
|
||||
| ^^^^^^^^^^^^^^^^^^ PTH111
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 |-bb = os.path.expanduser(p)
|
||||
19 |+bb = pathlib.Path(p).expanduser()
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
|
||||
full_name.py:19:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
17 | b = os.path.exists(p)
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
| ^^^^^^^^^^^^^ PTH112
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 |-bbb = os.path.isdir(p)
|
||||
20 |+bbb = pathlib.Path(p).is_dir()
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 23 | os.readlink(p)
|
||||
|
||||
full_name.py:20:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
| ^^^^^^^^^^^^^^ PTH113
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 |-bbbb = os.path.isfile(p)
|
||||
21 |+bbbb = pathlib.Path(p).is_file()
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 23 | os.readlink(p)
|
||||
23 24 | os.stat(p)
|
||||
|
||||
full_name.py:21:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
| ^^^^^^^^^^^^^^ PTH114
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 |-bbbbb = os.path.islink(p)
|
||||
22 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
22 23 | os.readlink(p)
|
||||
23 24 | os.stat(p)
|
||||
24 25 | os.path.isabs(p)
|
||||
|
||||
full_name.py:22:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
| ^^^^^^^^^^^ PTH115
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 |-os.readlink(p)
|
||||
23 |+pathlib.Path(p).readlink()
|
||||
23 24 | os.stat(p)
|
||||
24 25 | os.path.isabs(p)
|
||||
25 26 | os.path.join(p, q)
|
||||
|
||||
full_name.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
| ^^^^^^^ PTH116
|
||||
24 | os.path.isabs(p)
|
||||
25 | os.path.join(p, q)
|
||||
|
|
||||
|
||||
full_name.py:24:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
| ^^^^^^^^^^^^^ PTH117
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 23 | os.readlink(p)
|
||||
23 24 | os.stat(p)
|
||||
24 |-os.path.isabs(p)
|
||||
25 |+pathlib.Path(p).is_absolute()
|
||||
25 26 | os.path.join(p, q)
|
||||
26 27 | os.sep.join([p, q])
|
||||
27 28 | os.sep.join((p, q))
|
||||
|
||||
full_name.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
25 | os.path.join(p, q)
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
26 | os.sep.join([p, q])
|
||||
27 | os.sep.join((p, q))
|
||||
|
|
||||
|
||||
full_name.py:26:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
24 | os.path.isabs(p)
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
| ^^^^^^^^^^^ PTH118
|
||||
27 | os.sep.join((p, q))
|
||||
28 | os.path.basename(p)
|
||||
|
|
||||
|
||||
full_name.py:27:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
27 | os.sep.join((p, q))
|
||||
| ^^^^^^^^^^^ PTH118
|
||||
28 | os.path.basename(p)
|
||||
29 | os.path.dirname(p)
|
||||
|
|
||||
|
||||
full_name.py:28:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
26 | os.sep.join([p, q])
|
||||
27 | os.sep.join((p, q))
|
||||
28 | os.path.basename(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH119
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | os.path.join(p, q)
|
||||
26 27 | os.sep.join([p, q])
|
||||
27 28 | os.sep.join((p, q))
|
||||
28 |-os.path.basename(p)
|
||||
29 |+pathlib.Path(p).name
|
||||
29 30 | os.path.dirname(p)
|
||||
30 31 | os.path.samefile(p)
|
||||
31 32 | os.path.splitext(p)
|
||||
|
||||
full_name.py:29:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
27 | os.sep.join((p, q))
|
||||
28 | os.path.basename(p)
|
||||
29 | os.path.dirname(p)
|
||||
| ^^^^^^^^^^^^^^^ PTH120
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | os.sep.join([p, q])
|
||||
27 28 | os.sep.join((p, q))
|
||||
28 29 | os.path.basename(p)
|
||||
29 |-os.path.dirname(p)
|
||||
30 |+pathlib.Path(p).parent
|
||||
30 31 | os.path.samefile(p)
|
||||
31 32 | os.path.splitext(p)
|
||||
32 33 | with open(p) as fp:
|
||||
|
||||
full_name.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
28 | os.path.basename(p)
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH121
|
||||
31 | os.path.splitext(p)
|
||||
32 | with open(p) as fp:
|
||||
|
|
||||
|
||||
full_name.py:31:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH122
|
||||
32 | with open(p) as fp:
|
||||
33 | fp.read()
|
||||
|
|
||||
|
||||
full_name.py:32:6: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
32 | with open(p) as fp:
|
||||
| ^^^^ PTH123
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
|
|
||||
|
||||
full_name.py:34:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
32 | with open(p) as fp:
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
| ^^^^ PTH123
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
|
|
||||
|
||||
full_name.py:35:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
35 | os.getcwdb(p)
|
||||
| ^^^^^^^^^^ PTH109
|
||||
36 | os.path.join(p, *q)
|
||||
37 | os.sep.join(p, *q)
|
||||
|
|
||||
|
||||
full_name.py:36:1: PTH118 `os.path.join()` should be replaced by `Path.joinpath()`
|
||||
|
|
||||
34 | open(p).close()
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
37 | os.sep.join(p, *q)
|
||||
|
|
||||
|
||||
full_name.py:37:1: PTH118 `os.sep.join()` should be replaced by `Path.joinpath()`
|
||||
|
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
37 | os.sep.join(p, *q)
|
||||
| ^^^^^^^^^^^ PTH118
|
||||
38 |
|
||||
39 | # https://github.com/astral-sh/ruff/issues/7620
|
||||
|
|
||||
|
||||
full_name.py:46:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
44 | open(p, closefd=False)
|
||||
45 | open(p, opener=opener)
|
||||
46 | open(p, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
| ^^^^ PTH123
|
||||
47 | open(p, 'r', - 1, None, None, None, True, None)
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
|
||||
full_name.py:47:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
45 | open(p, opener=opener)
|
||||
46 | open(p, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
47 | open(p, 'r', - 1, None, None, None, True, None)
|
||||
| ^^^^ PTH123
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
|
||||
full_name.py:65:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
63 | open(f())
|
||||
64 |
|
||||
65 | open(b"foo")
|
||||
| ^^^^ PTH123
|
||||
66 | byte_str = b"bar"
|
||||
67 | open(byte_str)
|
||||
|
|
||||
|
||||
full_name.py:67:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
65 | open(b"foo")
|
||||
66 | byte_str = b"bar"
|
||||
67 | open(byte_str)
|
||||
| ^^^^ PTH123
|
||||
68 |
|
||||
69 | def bytes_str_func() -> bytes:
|
||||
|
|
||||
|
||||
full_name.py:71:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
69 | def bytes_str_func() -> bytes:
|
||||
70 | return b"foo"
|
||||
71 | open(bytes_str_func())
|
||||
| ^^^^ PTH123
|
||||
72 |
|
||||
73 | # https://github.com/astral-sh/ruff/issues/17693
|
||||
|
|
||||
@@ -0,0 +1,478 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
import_as.py:7:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
5 | q = "bar"
|
||||
6 |
|
||||
7 | a = foo_p.abspath(p)
|
||||
| ^^^^^^^^^^^^^ PTH100
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
6 7 |
|
||||
7 |-a = foo_p.abspath(p)
|
||||
8 |+a = pathlib.Path(p).resolve()
|
||||
8 9 | aa = foo.chmod(p)
|
||||
9 10 | aaa = foo.mkdir(p)
|
||||
10 11 | foo.makedirs(p)
|
||||
|
||||
import_as.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
7 | a = foo_p.abspath(p)
|
||||
8 | aa = foo.chmod(p)
|
||||
| ^^^^^^^^^ PTH101
|
||||
9 | aaa = foo.mkdir(p)
|
||||
10 | foo.makedirs(p)
|
||||
|
|
||||
|
||||
import_as.py:9:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
7 | a = foo_p.abspath(p)
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
| ^^^^^^^^^ PTH102
|
||||
10 | foo.makedirs(p)
|
||||
11 | foo.rename(p)
|
||||
|
|
||||
|
||||
import_as.py:10:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
10 | foo.makedirs(p)
|
||||
| ^^^^^^^^^^^^ PTH103
|
||||
11 | foo.rename(p)
|
||||
12 | foo.replace(p)
|
||||
|
|
||||
|
||||
import_as.py:11:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
9 | aaa = foo.mkdir(p)
|
||||
10 | foo.makedirs(p)
|
||||
11 | foo.rename(p)
|
||||
| ^^^^^^^^^^ PTH104
|
||||
12 | foo.replace(p)
|
||||
13 | foo.rmdir(p)
|
||||
|
|
||||
|
||||
import_as.py:12:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
10 | foo.makedirs(p)
|
||||
11 | foo.rename(p)
|
||||
12 | foo.replace(p)
|
||||
| ^^^^^^^^^^^ PTH105
|
||||
13 | foo.rmdir(p)
|
||||
14 | foo.remove(p)
|
||||
|
|
||||
|
||||
import_as.py:13:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
11 | foo.rename(p)
|
||||
12 | foo.replace(p)
|
||||
13 | foo.rmdir(p)
|
||||
| ^^^^^^^^^ PTH106
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
10 11 | foo.makedirs(p)
|
||||
11 12 | foo.rename(p)
|
||||
12 13 | foo.replace(p)
|
||||
13 |-foo.rmdir(p)
|
||||
14 |+pathlib.Path(p).rmdir()
|
||||
14 15 | foo.remove(p)
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
|
||||
import_as.py:14:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
12 | foo.replace(p)
|
||||
13 | foo.rmdir(p)
|
||||
14 | foo.remove(p)
|
||||
| ^^^^^^^^^^ PTH107
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
11 12 | foo.rename(p)
|
||||
12 13 | foo.replace(p)
|
||||
13 14 | foo.rmdir(p)
|
||||
14 |-foo.remove(p)
|
||||
15 |+pathlib.Path(p).unlink()
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
|
||||
import_as.py:15:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
13 | foo.rmdir(p)
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
| ^^^^^^^^^^ PTH108
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
12 13 | foo.replace(p)
|
||||
13 14 | foo.rmdir(p)
|
||||
14 15 | foo.remove(p)
|
||||
15 |-foo.unlink(p)
|
||||
16 |+pathlib.Path(p).unlink()
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
|
||||
import_as.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
| ^^^^^^^^^^ PTH109
|
||||
17 | b = foo_p.exists(p)
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
|
|
||||
|
||||
import_as.py:17:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
| ^^^^^^^^^^^^ PTH110
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
14 15 | foo.remove(p)
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
17 |-b = foo_p.exists(p)
|
||||
18 |+b = pathlib.Path(p).exists()
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
|
||||
import_as.py:18:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH111
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 |-bb = foo_p.expanduser(p)
|
||||
19 |+bb = pathlib.Path(p).expanduser()
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
|
||||
import_as.py:19:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
17 | b = foo_p.exists(p)
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
| ^^^^^^^^^^^ PTH112
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 |-bbb = foo_p.isdir(p)
|
||||
20 |+bbb = pathlib.Path(p).is_dir()
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 23 | foo.readlink(p)
|
||||
|
||||
import_as.py:20:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
| ^^^^^^^^^^^^ PTH113
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 |-bbbb = foo_p.isfile(p)
|
||||
21 |+bbbb = pathlib.Path(p).is_file()
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 23 | foo.readlink(p)
|
||||
23 24 | foo.stat(p)
|
||||
|
||||
import_as.py:21:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
| ^^^^^^^^^^^^ PTH114
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 |-bbbbb = foo_p.islink(p)
|
||||
22 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
22 23 | foo.readlink(p)
|
||||
23 24 | foo.stat(p)
|
||||
24 25 | foo_p.isabs(p)
|
||||
|
||||
import_as.py:22:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
| ^^^^^^^^^^^^ PTH115
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 |-foo.readlink(p)
|
||||
23 |+pathlib.Path(p).readlink()
|
||||
23 24 | foo.stat(p)
|
||||
24 25 | foo_p.isabs(p)
|
||||
25 26 | foo_p.join(p, q)
|
||||
|
||||
import_as.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
| ^^^^^^^^ PTH116
|
||||
24 | foo_p.isabs(p)
|
||||
25 | foo_p.join(p, q)
|
||||
|
|
||||
|
||||
import_as.py:24:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
| ^^^^^^^^^^^ PTH117
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 23 | foo.readlink(p)
|
||||
23 24 | foo.stat(p)
|
||||
24 |-foo_p.isabs(p)
|
||||
25 |+pathlib.Path(p).is_absolute()
|
||||
25 26 | foo_p.join(p, q)
|
||||
26 27 | foo.sep.join([p, q])
|
||||
27 28 | foo.sep.join((p, q))
|
||||
|
||||
import_as.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
25 | foo_p.join(p, q)
|
||||
| ^^^^^^^^^^ PTH118
|
||||
26 | foo.sep.join([p, q])
|
||||
27 | foo.sep.join((p, q))
|
||||
|
|
||||
|
||||
import_as.py:26:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
24 | foo_p.isabs(p)
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
27 | foo.sep.join((p, q))
|
||||
28 | foo_p.basename(p)
|
||||
|
|
||||
|
||||
import_as.py:27:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
27 | foo.sep.join((p, q))
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
28 | foo_p.basename(p)
|
||||
29 | foo_p.dirname(p)
|
||||
|
|
||||
|
||||
import_as.py:28:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
26 | foo.sep.join([p, q])
|
||||
27 | foo.sep.join((p, q))
|
||||
28 | foo_p.basename(p)
|
||||
| ^^^^^^^^^^^^^^ PTH119
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | foo_p.join(p, q)
|
||||
26 27 | foo.sep.join([p, q])
|
||||
27 28 | foo.sep.join((p, q))
|
||||
28 |-foo_p.basename(p)
|
||||
29 |+pathlib.Path(p).name
|
||||
29 30 | foo_p.dirname(p)
|
||||
30 31 | foo_p.samefile(p)
|
||||
31 32 | foo_p.splitext(p)
|
||||
|
||||
import_as.py:29:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
27 | foo.sep.join((p, q))
|
||||
28 | foo_p.basename(p)
|
||||
29 | foo_p.dirname(p)
|
||||
| ^^^^^^^^^^^^^ PTH120
|
||||
30 | foo_p.samefile(p)
|
||||
31 | foo_p.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | foo.sep.join([p, q])
|
||||
27 28 | foo.sep.join((p, q))
|
||||
28 29 | foo_p.basename(p)
|
||||
29 |-foo_p.dirname(p)
|
||||
30 |+pathlib.Path(p).parent
|
||||
30 31 | foo_p.samefile(p)
|
||||
31 32 | foo_p.splitext(p)
|
||||
|
||||
import_as.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
28 | foo_p.basename(p)
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
| ^^^^^^^^^^^^^^ PTH121
|
||||
31 | foo_p.splitext(p)
|
||||
|
|
||||
|
||||
import_as.py:31:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
31 | foo_p.splitext(p)
|
||||
| ^^^^^^^^^^^^^^ PTH122
|
||||
|
|
||||
@@ -0,0 +1,521 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
import_from.py:9:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
7 | q = "bar"
|
||||
8 |
|
||||
9 | a = abspath(p)
|
||||
| ^^^^^^^ PTH100
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
8 9 |
|
||||
9 |-a = abspath(p)
|
||||
10 |+a = pathlib.Path(p).resolve()
|
||||
10 11 | aa = chmod(p)
|
||||
11 12 | aaa = mkdir(p)
|
||||
12 13 | makedirs(p)
|
||||
|
||||
import_from.py:10:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
9 | a = abspath(p)
|
||||
10 | aa = chmod(p)
|
||||
| ^^^^^ PTH101
|
||||
11 | aaa = mkdir(p)
|
||||
12 | makedirs(p)
|
||||
|
|
||||
|
||||
import_from.py:11:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
9 | a = abspath(p)
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
| ^^^^^ PTH102
|
||||
12 | makedirs(p)
|
||||
13 | rename(p)
|
||||
|
|
||||
|
||||
import_from.py:12:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
12 | makedirs(p)
|
||||
| ^^^^^^^^ PTH103
|
||||
13 | rename(p)
|
||||
14 | replace(p)
|
||||
|
|
||||
|
||||
import_from.py:13:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
11 | aaa = mkdir(p)
|
||||
12 | makedirs(p)
|
||||
13 | rename(p)
|
||||
| ^^^^^^ PTH104
|
||||
14 | replace(p)
|
||||
15 | rmdir(p)
|
||||
|
|
||||
|
||||
import_from.py:14:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
12 | makedirs(p)
|
||||
13 | rename(p)
|
||||
14 | replace(p)
|
||||
| ^^^^^^^ PTH105
|
||||
15 | rmdir(p)
|
||||
16 | remove(p)
|
||||
|
|
||||
|
||||
import_from.py:15:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
13 | rename(p)
|
||||
14 | replace(p)
|
||||
15 | rmdir(p)
|
||||
| ^^^^^ PTH106
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
12 13 | makedirs(p)
|
||||
13 14 | rename(p)
|
||||
14 15 | replace(p)
|
||||
15 |-rmdir(p)
|
||||
16 |+pathlib.Path(p).rmdir()
|
||||
16 17 | remove(p)
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
|
||||
import_from.py:16:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
14 | replace(p)
|
||||
15 | rmdir(p)
|
||||
16 | remove(p)
|
||||
| ^^^^^^ PTH107
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
13 14 | rename(p)
|
||||
14 15 | replace(p)
|
||||
15 16 | rmdir(p)
|
||||
16 |-remove(p)
|
||||
17 |+pathlib.Path(p).unlink()
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
|
||||
import_from.py:17:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
15 | rmdir(p)
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
| ^^^^^^ PTH108
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
14 15 | replace(p)
|
||||
15 16 | rmdir(p)
|
||||
16 17 | remove(p)
|
||||
17 |-unlink(p)
|
||||
18 |+pathlib.Path(p).unlink()
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
20 21 | bb = expanduser(p)
|
||||
|
||||
import_from.py:18:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
| ^^^^^^ PTH109
|
||||
19 | b = exists(p)
|
||||
20 | bb = expanduser(p)
|
||||
|
|
||||
|
||||
import_from.py:19:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
| ^^^^^^ PTH110
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
16 17 | remove(p)
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
19 |-b = exists(p)
|
||||
20 |+b = pathlib.Path(p).exists()
|
||||
20 21 | bb = expanduser(p)
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
|
||||
import_from.py:20:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
20 | bb = expanduser(p)
|
||||
| ^^^^^^^^^^ PTH111
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
20 |-bb = expanduser(p)
|
||||
21 |+bb = pathlib.Path(p).expanduser()
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 24 | bbbbb = islink(p)
|
||||
|
||||
import_from.py:21:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
19 | b = exists(p)
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
| ^^^^^ PTH112
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
20 21 | bb = expanduser(p)
|
||||
21 |-bbb = isdir(p)
|
||||
22 |+bbb = pathlib.Path(p).is_dir()
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 25 | readlink(p)
|
||||
|
||||
import_from.py:22:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
| ^^^^^^ PTH113
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | b = exists(p)
|
||||
20 21 | bb = expanduser(p)
|
||||
21 22 | bbb = isdir(p)
|
||||
22 |-bbbb = isfile(p)
|
||||
23 |+bbbb = pathlib.Path(p).is_file()
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 25 | readlink(p)
|
||||
25 26 | stat(p)
|
||||
|
||||
import_from.py:23:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
| ^^^^^^ PTH114
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
20 21 | bb = expanduser(p)
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 |-bbbbb = islink(p)
|
||||
24 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
24 25 | readlink(p)
|
||||
25 26 | stat(p)
|
||||
26 27 | isabs(p)
|
||||
|
||||
import_from.py:24:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
| ^^^^^^^^ PTH115
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 |-readlink(p)
|
||||
25 |+pathlib.Path(p).readlink()
|
||||
25 26 | stat(p)
|
||||
26 27 | isabs(p)
|
||||
27 28 | join(p, q)
|
||||
|
||||
import_from.py:25:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
| ^^^^ PTH116
|
||||
26 | isabs(p)
|
||||
27 | join(p, q)
|
||||
|
|
||||
|
||||
import_from.py:26:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
| ^^^^^ PTH117
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 25 | readlink(p)
|
||||
25 26 | stat(p)
|
||||
26 |-isabs(p)
|
||||
27 |+pathlib.Path(p).is_absolute()
|
||||
27 28 | join(p, q)
|
||||
28 29 | sep.join((p, q))
|
||||
29 30 | sep.join([p, q])
|
||||
|
||||
import_from.py:27:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
27 | join(p, q)
|
||||
| ^^^^ PTH118
|
||||
28 | sep.join((p, q))
|
||||
29 | sep.join([p, q])
|
||||
|
|
||||
|
||||
import_from.py:28:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
26 | isabs(p)
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
| ^^^^^^^^ PTH118
|
||||
29 | sep.join([p, q])
|
||||
30 | basename(p)
|
||||
|
|
||||
|
||||
import_from.py:29:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
29 | sep.join([p, q])
|
||||
| ^^^^^^^^ PTH118
|
||||
30 | basename(p)
|
||||
31 | dirname(p)
|
||||
|
|
||||
|
||||
import_from.py:30:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
28 | sep.join((p, q))
|
||||
29 | sep.join([p, q])
|
||||
30 | basename(p)
|
||||
| ^^^^^^^^ PTH119
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
27 28 | join(p, q)
|
||||
28 29 | sep.join((p, q))
|
||||
29 30 | sep.join([p, q])
|
||||
30 |-basename(p)
|
||||
31 |+pathlib.Path(p).name
|
||||
31 32 | dirname(p)
|
||||
32 33 | samefile(p)
|
||||
33 34 | splitext(p)
|
||||
|
||||
import_from.py:31:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
29 | sep.join([p, q])
|
||||
30 | basename(p)
|
||||
31 | dirname(p)
|
||||
| ^^^^^^^ PTH120
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
28 29 | sep.join((p, q))
|
||||
29 30 | sep.join([p, q])
|
||||
30 31 | basename(p)
|
||||
31 |-dirname(p)
|
||||
32 |+pathlib.Path(p).parent
|
||||
32 33 | samefile(p)
|
||||
33 34 | splitext(p)
|
||||
34 35 | with open(p) as fp:
|
||||
|
||||
import_from.py:32:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
30 | basename(p)
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
| ^^^^^^^^ PTH121
|
||||
33 | splitext(p)
|
||||
34 | with open(p) as fp:
|
||||
|
|
||||
|
||||
import_from.py:33:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
| ^^^^^^^^ PTH122
|
||||
34 | with open(p) as fp:
|
||||
35 | fp.read()
|
||||
|
|
||||
|
||||
import_from.py:34:6: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
34 | with open(p) as fp:
|
||||
| ^^^^ PTH123
|
||||
35 | fp.read()
|
||||
36 | open(p).close()
|
||||
|
|
||||
|
||||
import_from.py:36:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
34 | with open(p) as fp:
|
||||
35 | fp.read()
|
||||
36 | open(p).close()
|
||||
| ^^^^ PTH123
|
||||
|
|
||||
|
||||
import_from.py:43:10: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
41 | from builtins import open
|
||||
42 |
|
||||
43 | with open(p) as _: ... # Error
|
||||
| ^^^^ PTH123
|
||||
|
|
||||
@@ -0,0 +1,491 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
import_from_as.py:14:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
12 | q = "bar"
|
||||
13 |
|
||||
14 | a = xabspath(p)
|
||||
| ^^^^^^^^ PTH100
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
13 14 |
|
||||
14 |-a = xabspath(p)
|
||||
15 |+a = pathlib.Path(p).resolve()
|
||||
15 16 | aa = xchmod(p)
|
||||
16 17 | aaa = xmkdir(p)
|
||||
17 18 | xmakedirs(p)
|
||||
|
||||
import_from_as.py:15:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
14 | a = xabspath(p)
|
||||
15 | aa = xchmod(p)
|
||||
| ^^^^^^ PTH101
|
||||
16 | aaa = xmkdir(p)
|
||||
17 | xmakedirs(p)
|
||||
|
|
||||
|
||||
import_from_as.py:16:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
14 | a = xabspath(p)
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
| ^^^^^^ PTH102
|
||||
17 | xmakedirs(p)
|
||||
18 | xrename(p)
|
||||
|
|
||||
|
||||
import_from_as.py:17:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
17 | xmakedirs(p)
|
||||
| ^^^^^^^^^ PTH103
|
||||
18 | xrename(p)
|
||||
19 | xreplace(p)
|
||||
|
|
||||
|
||||
import_from_as.py:18:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
16 | aaa = xmkdir(p)
|
||||
17 | xmakedirs(p)
|
||||
18 | xrename(p)
|
||||
| ^^^^^^^ PTH104
|
||||
19 | xreplace(p)
|
||||
20 | xrmdir(p)
|
||||
|
|
||||
|
||||
import_from_as.py:19:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
17 | xmakedirs(p)
|
||||
18 | xrename(p)
|
||||
19 | xreplace(p)
|
||||
| ^^^^^^^^ PTH105
|
||||
20 | xrmdir(p)
|
||||
21 | xremove(p)
|
||||
|
|
||||
|
||||
import_from_as.py:20:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
18 | xrename(p)
|
||||
19 | xreplace(p)
|
||||
20 | xrmdir(p)
|
||||
| ^^^^^^ PTH106
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | xmakedirs(p)
|
||||
18 19 | xrename(p)
|
||||
19 20 | xreplace(p)
|
||||
20 |-xrmdir(p)
|
||||
21 |+pathlib.Path(p).rmdir()
|
||||
21 22 | xremove(p)
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
|
||||
import_from_as.py:21:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
19 | xreplace(p)
|
||||
20 | xrmdir(p)
|
||||
21 | xremove(p)
|
||||
| ^^^^^^^ PTH107
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | xrename(p)
|
||||
19 20 | xreplace(p)
|
||||
20 21 | xrmdir(p)
|
||||
21 |-xremove(p)
|
||||
22 |+pathlib.Path(p).unlink()
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
|
||||
import_from_as.py:22:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
20 | xrmdir(p)
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
| ^^^^^^^ PTH108
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | xreplace(p)
|
||||
20 21 | xrmdir(p)
|
||||
21 22 | xremove(p)
|
||||
22 |-xunlink(p)
|
||||
23 |+pathlib.Path(p).unlink()
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
25 26 | bb = xexpanduser(p)
|
||||
|
||||
import_from_as.py:23:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
| ^^^^^^^ PTH109
|
||||
24 | b = xexists(p)
|
||||
25 | bb = xexpanduser(p)
|
||||
|
|
||||
|
||||
import_from_as.py:24:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
| ^^^^^^^ PTH110
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | xremove(p)
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
24 |-b = xexists(p)
|
||||
25 |+b = pathlib.Path(p).exists()
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
|
||||
import_from_as.py:25:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
25 | bb = xexpanduser(p)
|
||||
| ^^^^^^^^^^^ PTH111
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
25 |-bb = xexpanduser(p)
|
||||
26 |+bb = pathlib.Path(p).expanduser()
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 29 | bbbbb = xislink(p)
|
||||
|
||||
import_from_as.py:26:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
24 | b = xexists(p)
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
| ^^^^^^ PTH112
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 |-bbb = xisdir(p)
|
||||
27 |+bbb = pathlib.Path(p).is_dir()
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 30 | xreadlink(p)
|
||||
|
||||
import_from_as.py:27:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
| ^^^^^^^ PTH113
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
24 25 | b = xexists(p)
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 |-bbbb = xisfile(p)
|
||||
28 |+bbbb = pathlib.Path(p).is_file()
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 30 | xreadlink(p)
|
||||
30 31 | xstat(p)
|
||||
|
||||
import_from_as.py:28:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
| ^^^^^^^ PTH114
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 |-bbbbb = xislink(p)
|
||||
29 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
29 30 | xreadlink(p)
|
||||
30 31 | xstat(p)
|
||||
31 32 | xisabs(p)
|
||||
|
||||
import_from_as.py:29:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
| ^^^^^^^^^ PTH115
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 |-xreadlink(p)
|
||||
30 |+pathlib.Path(p).readlink()
|
||||
30 31 | xstat(p)
|
||||
31 32 | xisabs(p)
|
||||
32 33 | xjoin(p, q)
|
||||
|
||||
import_from_as.py:30:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
| ^^^^^ PTH116
|
||||
31 | xisabs(p)
|
||||
32 | xjoin(p, q)
|
||||
|
|
||||
|
||||
import_from_as.py:31:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
| ^^^^^^ PTH117
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 30 | xreadlink(p)
|
||||
30 31 | xstat(p)
|
||||
31 |-xisabs(p)
|
||||
32 |+pathlib.Path(p).is_absolute()
|
||||
32 33 | xjoin(p, q)
|
||||
33 34 | s.join((p, q))
|
||||
34 35 | s.join([p, q])
|
||||
|
||||
import_from_as.py:32:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
32 | xjoin(p, q)
|
||||
| ^^^^^ PTH118
|
||||
33 | s.join((p, q))
|
||||
34 | s.join([p, q])
|
||||
|
|
||||
|
||||
import_from_as.py:33:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
31 | xisabs(p)
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
| ^^^^^^ PTH118
|
||||
34 | s.join([p, q])
|
||||
35 | xbasename(p)
|
||||
|
|
||||
|
||||
import_from_as.py:34:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
34 | s.join([p, q])
|
||||
| ^^^^^^ PTH118
|
||||
35 | xbasename(p)
|
||||
36 | xdirname(p)
|
||||
|
|
||||
|
||||
import_from_as.py:35:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
33 | s.join((p, q))
|
||||
34 | s.join([p, q])
|
||||
35 | xbasename(p)
|
||||
| ^^^^^^^^^ PTH119
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
32 33 | xjoin(p, q)
|
||||
33 34 | s.join((p, q))
|
||||
34 35 | s.join([p, q])
|
||||
35 |-xbasename(p)
|
||||
36 |+pathlib.Path(p).name
|
||||
36 37 | xdirname(p)
|
||||
37 38 | xsamefile(p)
|
||||
38 39 | xsplitext(p)
|
||||
|
||||
import_from_as.py:36:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
34 | s.join([p, q])
|
||||
35 | xbasename(p)
|
||||
36 | xdirname(p)
|
||||
| ^^^^^^^^ PTH120
|
||||
37 | xsamefile(p)
|
||||
38 | xsplitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
33 34 | s.join((p, q))
|
||||
34 35 | s.join([p, q])
|
||||
35 36 | xbasename(p)
|
||||
36 |-xdirname(p)
|
||||
37 |+pathlib.Path(p).parent
|
||||
37 38 | xsamefile(p)
|
||||
38 39 | xsplitext(p)
|
||||
|
||||
import_from_as.py:37:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
35 | xbasename(p)
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
| ^^^^^^^^^ PTH121
|
||||
38 | xsplitext(p)
|
||||
|
|
||||
|
||||
import_from_as.py:38:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
38 | xsplitext(p)
|
||||
| ^^^^^^^^^ PTH122
|
||||
|
|
||||
@@ -2,51 +2,6 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use crate::Violation;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.abspath`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.resolve()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.abspath()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// file_path = os.path.abspath("../path/to/file")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// file_path = Path("../path/to/file").resolve()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
|
||||
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathAbspath;
|
||||
|
||||
impl Violation for OsPathAbspath {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.abspath()` should be replaced by `Path.resolve()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.chmod`.
|
||||
///
|
||||
@@ -275,141 +230,6 @@ impl Violation for OsReplace {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.rmdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.rmdir()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.rmdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.rmdir("folder/")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("folder/").rmdir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
|
||||
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRmdir;
|
||||
|
||||
impl Violation for OsRmdir {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.rmdir()` should be replaced by `Path.rmdir()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.remove`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.remove()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.remove("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRemove;
|
||||
|
||||
impl Violation for OsRemove {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.remove()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.unlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.unlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.unlink("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsUnlink;
|
||||
|
||||
impl Violation for OsUnlink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.unlink()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.getcwd` and `os.getcwdb`.
|
||||
///
|
||||
@@ -456,276 +276,6 @@ impl Violation for OsGetcwd {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.exists`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.exists()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.exists()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.exists("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").exists()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
|
||||
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExists;
|
||||
|
||||
impl Violation for OsPathExists {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.exists()` should be replaced by `Path.exists()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.expanduser`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.expanduser()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.expanduser()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.expanduser("~/films/Monty Python")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("~/films/Monty Python").expanduser()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExpanduser;
|
||||
|
||||
impl Violation for OsPathExpanduser {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.expanduser()` should be replaced by `Path.expanduser()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_dir()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isdir("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_dir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
|
||||
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsdir;
|
||||
|
||||
impl Violation for OsPathIsdir {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isdir()` should be replaced by `Path.is_dir()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isfile`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_file()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isfile()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isfile("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_file()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
|
||||
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsfile;
|
||||
|
||||
impl Violation for OsPathIsfile {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isfile()` should be replaced by `Path.is_file()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.islink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_symlink()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.islink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.islink("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_symlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIslink;
|
||||
|
||||
impl Violation for OsPathIslink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.islink()` should be replaced by `Path.is_symlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.readlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.readlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.readlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.readlink(file_name)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(file_name).readlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
|
||||
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsReadlink;
|
||||
|
||||
impl Violation for OsReadlink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.readlink()` should be replaced by `Path.readlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.stat`.
|
||||
///
|
||||
@@ -781,53 +331,6 @@ impl Violation for OsStat {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isabs`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_absolute()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.isabs()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// if os.path.isabs(file_name):
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// if Path(file_name).is_absolute():
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
|
||||
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsabs;
|
||||
|
||||
impl Violation for OsPathIsabs {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isabs()` should be replaced by `Path.is_absolute()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.join`.
|
||||
///
|
||||
@@ -890,96 +393,6 @@ pub(crate) enum Joiner {
|
||||
Joinpath,
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.basename`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.name` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.basename()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.basename(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).name
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathBasename;
|
||||
|
||||
impl Violation for OsPathBasename {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.basename()` should be replaced by `Path.name`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.dirname`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.parent` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.dirname()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.dirname(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).parent
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
|
||||
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathDirname;
|
||||
|
||||
impl Violation for OsPathDirname {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.dirname()` should be replaced by `Path.parent`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.samefile`.
|
||||
///
|
||||
|
||||
@@ -60,12 +60,14 @@ impl Violation for IndentationWithInvalidMultiple {
|
||||
/// ```python
|
||||
/// if True:
|
||||
/// # a = 1
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// if True:
|
||||
/// # a = 1
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
|
||||
@@ -43,12 +43,12 @@ impl AlwaysFixableViolation for MultipleSpacesAfterKeyword {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// True and False
|
||||
/// x and y
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// True and False
|
||||
/// x and y
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MultipleSpacesBeforeKeyword;
|
||||
|
||||
@@ -238,6 +238,9 @@ impl Violation for DocstringExtraneousYields {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class FasterThanLightError(ArithmeticError): ...
|
||||
///
|
||||
///
|
||||
/// def calculate_speed(distance: float, time: float) -> float:
|
||||
/// """Calculate speed as distance divided by time.
|
||||
///
|
||||
@@ -256,6 +259,9 @@ impl Violation for DocstringExtraneousYields {
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class FasterThanLightError(ArithmeticError): ...
|
||||
///
|
||||
///
|
||||
/// def calculate_speed(distance: float, time: float) -> float:
|
||||
/// """Calculate speed as distance divided by time.
|
||||
///
|
||||
|
||||
@@ -774,7 +774,7 @@ mod tests {
|
||||
messages.sort_by_key(|diagnostic| diagnostic.expect_range().start());
|
||||
let actual = messages
|
||||
.iter()
|
||||
.filter(|msg| !msg.is_syntax_error())
|
||||
.filter(|msg| !msg.is_invalid_syntax())
|
||||
.map(Diagnostic::name)
|
||||
.collect::<Vec<_>>();
|
||||
let expected: Vec<_> = expected.iter().map(|rule| rule.name().as_str()).collect();
|
||||
|
||||
@@ -10,11 +10,11 @@ use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for access to the first or last element of `str.split()` without
|
||||
/// Checks for access to the first or last element of `str.split()` or `str.rsplit()` without
|
||||
/// `maxsplit=1`
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling `str.split()` without `maxsplit` set splits on every delimiter in the
|
||||
/// Calling `str.split()` or `str.rsplit()` without passing `maxsplit=1` splits on every delimiter in the
|
||||
/// string. When accessing only the first or last element of the result, it
|
||||
/// would be more efficient to only split once.
|
||||
///
|
||||
@@ -29,14 +29,44 @@ use crate::checkers::ast::Checker;
|
||||
/// url = "www.example.com"
|
||||
/// prefix = url.split(".", maxsplit=1)[0]
|
||||
/// ```
|
||||
///
|
||||
/// To access the last element, use `str.rsplit()` instead of `str.split()`:
|
||||
/// ```python
|
||||
/// url = "www.example.com"
|
||||
/// suffix = url.rsplit(".", maxsplit=1)[-1]
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingMaxsplitArg;
|
||||
pub(crate) struct MissingMaxsplitArg {
|
||||
index: SliceBoundary,
|
||||
actual_split_type: String,
|
||||
}
|
||||
|
||||
/// Represents the index of the slice used for this rule (which can only be 0 or -1)
|
||||
enum SliceBoundary {
|
||||
First,
|
||||
Last,
|
||||
}
|
||||
|
||||
impl Violation for MissingMaxsplitArg {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Accessing only the first or last element of `str.split()` without setting `maxsplit=1`"
|
||||
.to_string()
|
||||
let MissingMaxsplitArg {
|
||||
index,
|
||||
actual_split_type,
|
||||
} = self;
|
||||
|
||||
let suggested_split_type = match index {
|
||||
SliceBoundary::First => "split",
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
|
||||
if actual_split_type == suggested_split_type {
|
||||
format!("Pass `maxsplit=1` into `str.{actual_split_type}()`")
|
||||
} else {
|
||||
format!(
|
||||
"Instead of `str.{actual_split_type}()`, call `str.{suggested_split_type}()` and pass `maxsplit=1`",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +112,11 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !matches!(index, Some(0 | -1)) {
|
||||
return;
|
||||
}
|
||||
let slice_boundary = match index {
|
||||
Some(0) => SliceBoundary::First,
|
||||
Some(-1) => SliceBoundary::Last,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let Expr::Attribute(ExprAttribute { attr, value, .. }) = func.as_ref() else {
|
||||
return;
|
||||
@@ -129,5 +161,11 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
}
|
||||
}
|
||||
|
||||
checker.report_diagnostic(MissingMaxsplitArg, expr.range());
|
||||
checker.report_diagnostic(
|
||||
MissingMaxsplitArg {
|
||||
index: slice_boundary,
|
||||
actual_split_type: attr.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
12 | # Errors
|
||||
13 | ## Test split called directly on string literal
|
||||
@@ -11,7 +11,7 @@ missing_maxsplit_arg.py:14:1: PLC0207 Accessing only the first or last element o
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
13 | ## Test split called directly on string literal
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -21,7 +21,7 @@ missing_maxsplit_arg.py:15:1: PLC0207 Accessing only the first or last element o
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -30,7 +30,7 @@ missing_maxsplit_arg.py:16:1: PLC0207 Accessing only the first or last element o
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -40,7 +40,7 @@ missing_maxsplit_arg.py:17:1: PLC0207 Accessing only the first or last element o
|
||||
19 | ## Test split called on string variable
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -49,7 +49,7 @@ missing_maxsplit_arg.py:20:1: PLC0207 Accessing only the first or last element o
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -59,7 +59,7 @@ missing_maxsplit_arg.py:21:1: PLC0207 Accessing only the first or last element o
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -68,7 +68,7 @@ missing_maxsplit_arg.py:22:1: PLC0207 Accessing only the first or last element o
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -78,7 +78,7 @@ missing_maxsplit_arg.py:23:1: PLC0207 Accessing only the first or last element o
|
||||
25 | ## Test split called on class attribute
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -87,7 +87,7 @@ missing_maxsplit_arg.py:26:1: PLC0207 Accessing only the first or last element o
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -97,7 +97,7 @@ missing_maxsplit_arg.py:27:1: PLC0207 Accessing only the first or last element o
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -106,7 +106,7 @@ missing_maxsplit_arg.py:28:1: PLC0207 Accessing only the first or last element o
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -116,7 +116,7 @@ missing_maxsplit_arg.py:29:1: PLC0207 Accessing only the first or last element o
|
||||
31 | ## Test split called on sliced string
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -125,7 +125,7 @@ missing_maxsplit_arg.py:32:1: PLC0207 Accessing only the first or last element o
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -135,7 +135,7 @@ missing_maxsplit_arg.py:33:1: PLC0207 Accessing only the first or last element o
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -145,7 +145,7 @@ missing_maxsplit_arg.py:34:1: PLC0207 Accessing only the first or last element o
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -155,7 +155,7 @@ missing_maxsplit_arg.py:35:1: PLC0207 Accessing only the first or last element o
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -165,7 +165,7 @@ missing_maxsplit_arg.py:36:1: PLC0207 Accessing only the first or last element o
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -174,7 +174,7 @@ missing_maxsplit_arg.py:37:1: PLC0207 Accessing only the first or last element o
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -184,7 +184,7 @@ missing_maxsplit_arg.py:38:1: PLC0207 Accessing only the first or last element o
|
||||
40 | ## Test sep given as named argument
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -193,7 +193,7 @@ missing_maxsplit_arg.py:41:1: PLC0207 Accessing only the first or last element o
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -203,7 +203,7 @@ missing_maxsplit_arg.py:42:1: PLC0207 Accessing only the first or last element o
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -212,7 +212,7 @@ missing_maxsplit_arg.py:43:1: PLC0207 Accessing only the first or last element o
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -222,7 +222,7 @@ missing_maxsplit_arg.py:44:1: PLC0207 Accessing only the first or last element o
|
||||
46 | ## Special cases
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -231,7 +231,7 @@ missing_maxsplit_arg.py:47:1: PLC0207 Accessing only the first or last element o
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -240,7 +240,7 @@ missing_maxsplit_arg.py:48:1: PLC0207 Accessing only the first or last element o
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
@@ -250,7 +250,7 @@ missing_maxsplit_arg.py:49:1: PLC0207 Accessing only the first or last element o
|
||||
51 | ## Test class attribute named split
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -259,7 +259,7 @@ missing_maxsplit_arg.py:52:1: PLC0207 Accessing only the first or last element o
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -269,7 +269,7 @@ missing_maxsplit_arg.py:53:1: PLC0207 Accessing only the first or last element o
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -278,7 +278,7 @@ missing_maxsplit_arg.py:54:1: PLC0207 Accessing only the first or last element o
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -288,14 +288,14 @@ missing_maxsplit_arg.py:55:1: PLC0207 Accessing only the first or last element o
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
177 | # Errors
|
||||
178 | kwargs_without_maxsplit = {"seq": ","}
|
||||
@@ -305,7 +305,7 @@ missing_maxsplit_arg.py:179:1: PLC0207 Accessing only the first or last element
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
180 | # OK
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
@@ -315,7 +315,7 @@ missing_maxsplit_arg.py:182:1: PLC0207 Accessing only the first or last element
|
||||
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
|
||||
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_safe_super_call_with_parameters_fix_enabled;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `super` calls that pass redundant arguments.
|
||||
@@ -57,14 +57,16 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SuperCallWithParameters;
|
||||
|
||||
impl AlwaysFixableViolation for SuperCallWithParameters {
|
||||
impl Violation for SuperCallWithParameters {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove `super()` parameters".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove `super()` parameters".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,22 +167,26 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
|
||||
return;
|
||||
}
|
||||
|
||||
let applicability = if !checker.comment_ranges().intersects(call.arguments.range())
|
||||
&& is_safe_super_call_with_parameters_fix_enabled(checker.settings())
|
||||
{
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(SuperCallWithParameters, call.arguments.range());
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::deletion(
|
||||
call.arguments.start() + TextSize::new(1),
|
||||
call.arguments.end() - TextSize::new(1),
|
||||
),
|
||||
applicability,
|
||||
));
|
||||
|
||||
// Only provide a fix if there are no keyword arguments, since super() doesn't accept keyword arguments
|
||||
if call.arguments.keywords.is_empty() {
|
||||
let applicability = if !checker.comment_ranges().intersects(call.arguments.range())
|
||||
&& is_safe_super_call_with_parameters_fix_enabled(checker.settings())
|
||||
{
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
};
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::deletion(
|
||||
call.arguments.start() + TextSize::new(1),
|
||||
call.arguments.end() - TextSize::new(1),
|
||||
),
|
||||
applicability,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a call is an argumented `super` invocation.
|
||||
|
||||
@@ -249,3 +249,53 @@ UP008.py:123:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
126 |- # also a comment
|
||||
127 |- ).f()
|
||||
123 |+ super().f()
|
||||
128 124 |
|
||||
129 125 |
|
||||
130 126 | # Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed
|
||||
|
||||
UP008.py:133:21: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
131 | class Ord(int):
|
||||
132 | def __len__(self):
|
||||
133 | return super(Ord, self, uhoh=True, **{"error": True}).bit_length()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
134 |
|
||||
135 | class ExampleWithKeywords:
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:137:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
135 | class ExampleWithKeywords:
|
||||
136 | def method1(self):
|
||||
137 | super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
138 |
|
||||
139 | def method2(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:140:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
139 | def method2(self):
|
||||
140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
141 |
|
||||
142 | def method3(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:143:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
142 | def method3(self):
|
||||
143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
ℹ Unsafe fix
|
||||
140 140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
141 141 |
|
||||
142 142 | def method3(self):
|
||||
143 |- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
143 |+ super().some_method() # Should be fixed - no keywords
|
||||
|
||||
@@ -249,3 +249,53 @@ UP008.py:123:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
126 |- # also a comment
|
||||
127 |- ).f()
|
||||
123 |+ super().f()
|
||||
128 124 |
|
||||
129 125 |
|
||||
130 126 | # Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed
|
||||
|
||||
UP008.py:133:21: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
131 | class Ord(int):
|
||||
132 | def __len__(self):
|
||||
133 | return super(Ord, self, uhoh=True, **{"error": True}).bit_length()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
134 |
|
||||
135 | class ExampleWithKeywords:
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:137:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
135 | class ExampleWithKeywords:
|
||||
136 | def method1(self):
|
||||
137 | super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
138 |
|
||||
139 | def method2(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:140:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
139 | def method2(self):
|
||||
140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
141 |
|
||||
142 | def method3(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:143:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
142 | def method3(self):
|
||||
143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
ℹ Safe fix
|
||||
140 140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
141 141 |
|
||||
142 142 | def method3(self):
|
||||
143 |- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
143 |+ super().some_method() # Should be fixed - no keywords
|
||||
|
||||
@@ -292,7 +292,7 @@ Either ensure you always emit a fix or change `Violation::FIX_AVAILABILITY` to e
|
||||
.chain(parsed.errors().iter().map(|parse_error| {
|
||||
create_syntax_error_diagnostic(source_code.clone(), &parse_error.error, parse_error)
|
||||
}))
|
||||
.sorted()
|
||||
.sorted_by(Diagnostic::ruff_start_ordering)
|
||||
.collect();
|
||||
(messages, transformed)
|
||||
}
|
||||
@@ -317,7 +317,7 @@ fn print_syntax_errors(errors: &[ParseError], path: &Path, source: &SourceKind)
|
||||
|
||||
/// Print the lint diagnostics in `diagnostics`.
|
||||
fn print_diagnostics(mut diagnostics: Vec<Diagnostic>, path: &Path, source: &SourceKind) -> String {
|
||||
diagnostics.retain(|msg| !msg.is_syntax_error());
|
||||
diagnostics.retain(|msg| !msg.is_invalid_syntax());
|
||||
|
||||
if let Some(notebook) = source.as_ipy_notebook() {
|
||||
print_jupyter_messages(&diagnostics, path, notebook)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use crate::codes::Rule;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::SourceFile;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::{codes::Rule, message::create_lint_diagnostic};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum FixAvailability {
|
||||
@@ -28,7 +32,7 @@ pub trait ViolationMetadata {
|
||||
fn explain() -> Option<&'static str>;
|
||||
}
|
||||
|
||||
pub trait Violation: ViolationMetadata {
|
||||
pub trait Violation: ViolationMetadata + Sized {
|
||||
/// `None` in the case a fix is never available or otherwise Some
|
||||
/// [`FixAvailability`] describing the available fix.
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
|
||||
@@ -48,6 +52,20 @@ pub trait Violation: ViolationMetadata {
|
||||
|
||||
/// Returns the format strings used by [`message`](Violation::message).
|
||||
fn message_formats() -> &'static [&'static str];
|
||||
|
||||
/// Convert the violation into a [`Diagnostic`].
|
||||
fn into_diagnostic(self, range: TextRange, file: &SourceFile) -> Diagnostic {
|
||||
create_lint_diagnostic(
|
||||
self.message(),
|
||||
self.fix_title(),
|
||||
range,
|
||||
None,
|
||||
None,
|
||||
file.clone(),
|
||||
None,
|
||||
Self::rule(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait exists just to make implementing the [`Violation`] trait more
|
||||
|
||||
95
crates/ruff_macros/src/env_vars.rs
Normal file
95
crates/ruff_macros/src/env_vars.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{ImplItem, ItemImpl};
|
||||
|
||||
pub(crate) fn attribute_env_vars_metadata(mut input: ItemImpl) -> TokenStream {
|
||||
// Verify that this is an impl for EnvVars
|
||||
let impl_type = &input.self_ty;
|
||||
|
||||
let mut env_var_entries = Vec::new();
|
||||
let mut hidden_vars = Vec::new();
|
||||
|
||||
// Process each item in the impl block
|
||||
for item in &mut input.items {
|
||||
if let ImplItem::Const(const_item) = item {
|
||||
// Extract the const name and value
|
||||
let const_name = &const_item.ident;
|
||||
let const_expr = &const_item.expr;
|
||||
|
||||
// Check if the const has the #[attr_hidden] attribute
|
||||
let is_hidden = const_item
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("attr_hidden"));
|
||||
|
||||
// Remove our custom attributes
|
||||
const_item.attrs.retain(|attr| {
|
||||
!attr.path().is_ident("attr_hidden")
|
||||
&& !attr.path().is_ident("attr_env_var_pattern")
|
||||
});
|
||||
|
||||
if is_hidden {
|
||||
hidden_vars.push(const_name.clone());
|
||||
} else {
|
||||
// Extract documentation from doc comments
|
||||
let doc_attrs: Vec<_> = const_item
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident("doc"))
|
||||
.collect();
|
||||
|
||||
if !doc_attrs.is_empty() {
|
||||
// Convert doc attributes to a single string
|
||||
let doc_string = extract_doc_string(&doc_attrs);
|
||||
env_var_entries.push((const_name.clone(), const_expr.clone(), doc_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the metadata method.
|
||||
let metadata_entries: Vec<_> = env_var_entries
|
||||
.iter()
|
||||
.map(|(_name, expr, doc)| {
|
||||
quote! {
|
||||
(#expr, #doc)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let metadata_impl = quote! {
|
||||
impl #impl_type {
|
||||
/// Returns metadata for all non-hidden environment variables.
|
||||
pub fn metadata() -> Vec<(&'static str, &'static str)> {
|
||||
vec![
|
||||
#(#metadata_entries),*
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
#input
|
||||
#metadata_impl
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract documentation from doc attributes into a single string
|
||||
fn extract_doc_string(attrs: &[&syn::Attribute]) -> String {
|
||||
attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
if let syn::Meta::NameValue(meta) = &attr.meta {
|
||||
if let syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit_str),
|
||||
..
|
||||
}) = &meta.value
|
||||
{
|
||||
return Some(lit_str.value().trim().to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//! This crate implements internal macros for the `ruff` library.
|
||||
//! This crate implements internal macros for the `ruff` and `ty` libraries.
|
||||
|
||||
use crate::cache_key::derive_cache_key;
|
||||
use crate::newtype_index::generate_newtype_index;
|
||||
@@ -11,6 +11,7 @@ mod combine;
|
||||
mod combine_options;
|
||||
mod config;
|
||||
mod derive_message_formats;
|
||||
mod env_vars;
|
||||
mod kebab_case;
|
||||
mod map_codes;
|
||||
mod newtype_index;
|
||||
@@ -144,3 +145,15 @@ pub fn newtype_index(_metadata: TokenStream, input: TokenStream) -> TokenStream
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
||||
|
||||
/// Generates metadata for environment variables declared in the impl block.
|
||||
///
|
||||
/// This attribute macro should be applied to an `impl EnvVars` block.
|
||||
/// It will generate a `metadata()` method that returns all non-hidden
|
||||
/// environment variables with their documentation.
|
||||
#[proc_macro_attribute]
|
||||
pub fn attribute_env_vars_metadata(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as syn::ItemImpl);
|
||||
|
||||
env_vars::attribute_env_vars_metadata(input).into()
|
||||
}
|
||||
|
||||
@@ -952,6 +952,9 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration { name, start: _ } => {
|
||||
write!(f, "name `{name}` is used prior to global declaration")
|
||||
}
|
||||
SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { name, start: _ } => {
|
||||
write!(f, "name `{name}` is used prior to nonlocal declaration")
|
||||
}
|
||||
SemanticSyntaxErrorKind::InvalidStarExpression => {
|
||||
f.write_str("Starred expression cannot be used here")
|
||||
}
|
||||
@@ -977,6 +980,18 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
write!(f, "nonlocal declaration not allowed at module level")
|
||||
}
|
||||
SemanticSyntaxErrorKind::NonlocalAndGlobal(name) => {
|
||||
write!(f, "name `{name}` is nonlocal and global")
|
||||
}
|
||||
SemanticSyntaxErrorKind::AnnotatedGlobal(name) => {
|
||||
write!(f, "annotated name `{name}` can't be global")
|
||||
}
|
||||
SemanticSyntaxErrorKind::AnnotatedNonlocal(name) => {
|
||||
write!(f, "annotated name `{name}` can't be nonlocal")
|
||||
}
|
||||
SemanticSyntaxErrorKind::InvalidNonlocal(name) => {
|
||||
write!(f, "no binding for nonlocal `{name}` found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1207,6 +1222,24 @@ pub enum SemanticSyntaxErrorKind {
|
||||
/// [#111123]: https://github.com/python/cpython/issues/111123
|
||||
LoadBeforeGlobalDeclaration { name: String, start: TextSize },
|
||||
|
||||
/// Represents the use of a `nonlocal` variable before its `nonlocal` declaration.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// counter = 0
|
||||
/// def increment():
|
||||
/// print(f"Adding 1 to {counter}")
|
||||
/// nonlocal counter # SyntaxError: name 'counter' is used prior to nonlocal declaration
|
||||
/// counter += 1
|
||||
/// ```
|
||||
///
|
||||
/// ## Known Issues
|
||||
///
|
||||
/// See [`LoadBeforeGlobalDeclaration`][Self::LoadBeforeGlobalDeclaration].
|
||||
LoadBeforeNonlocalDeclaration { name: String, start: TextSize },
|
||||
|
||||
/// Represents the use of a starred expression in an invalid location, such as a `return` or
|
||||
/// `yield` statement.
|
||||
///
|
||||
@@ -1307,6 +1340,41 @@ pub enum SemanticSyntaxErrorKind {
|
||||
|
||||
/// Represents a nonlocal declaration at module level
|
||||
NonlocalDeclarationAtModuleLevel,
|
||||
|
||||
/// Represents the same variable declared as both nonlocal and global
|
||||
NonlocalAndGlobal(String),
|
||||
|
||||
/// Represents a type annotation on a variable that's been declared global
|
||||
AnnotatedGlobal(String),
|
||||
|
||||
/// Represents a type annotation on a variable that's been declared nonlocal
|
||||
AnnotatedNonlocal(String),
|
||||
|
||||
/// Represents a nonlocal declaration with no definition in an enclosing scope
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// nonlocal x # error
|
||||
///
|
||||
/// # Global variables don't count.
|
||||
/// x = 1
|
||||
/// def f():
|
||||
/// nonlocal x # error
|
||||
///
|
||||
/// def f():
|
||||
/// x = 1
|
||||
/// def g():
|
||||
/// nonlocal x # allowed
|
||||
///
|
||||
/// # The definition can come later.
|
||||
/// def f():
|
||||
/// def g():
|
||||
/// nonlocal x # allowed
|
||||
/// x = 1
|
||||
/// ```
|
||||
InvalidNonlocal(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
|
||||
@@ -163,7 +163,7 @@ pub(crate) fn check(
|
||||
.into_iter()
|
||||
.zip(noqa_edits)
|
||||
.filter_map(|(message, noqa_edit)| {
|
||||
if message.is_syntax_error() && !show_syntax_errors {
|
||||
if message.is_invalid_syntax() && !show_syntax_errors {
|
||||
None
|
||||
} else {
|
||||
Some(to_lsp_diagnostic(
|
||||
|
||||
@@ -19,13 +19,13 @@ ruff_python_ast = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["zstd"] }
|
||||
ty_server = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
argfile = { workspace = true }
|
||||
clap = { workspace = true, features = ["wrap_help", "string", "env"] }
|
||||
clap_complete_command = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
countme = { workspace = true, features = ["enable"] }
|
||||
crossbeam = { workspace = true }
|
||||
ctrlc = { version = "3.4.4" }
|
||||
indicatif = { workspace = true }
|
||||
|
||||
3
crates/ty/docs/cli.md
generated
3
crates/ty/docs/cli.md
generated
@@ -84,7 +84,8 @@ over all configuration files.</p>
|
||||
<li><code>3.11</code></li>
|
||||
<li><code>3.12</code></li>
|
||||
<li><code>3.13</code></li>
|
||||
</ul></dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-gitignore</code> to disable</p>
|
||||
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a></dt><dd><p>Use quiet output</p>
|
||||
</dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-gitignore</code> to disable</p>
|
||||
</dd><dt id="ty-check--typeshed"><a href="#ty-check--typeshed"><code>--typeshed</code></a>, <code>--custom-typeshed-dir</code> <i>path</i></dt><dd><p>Custom directory to use for stdlib typeshed stubs</p>
|
||||
</dd><dt id="ty-check--verbose"><a href="#ty-check--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output (or <code>-vv</code> and <code>-vvv</code> for more verbose output)</p>
|
||||
</dd><dt id="ty-check--warn"><a href="#ty-check--warn"><code>--warn</code></a> <i>rule</i></dt><dd><p>Treat the given rule as having severity 'warn'. Can be specified multiple times.</p>
|
||||
|
||||
55
crates/ty/docs/environment.md
Normal file
55
crates/ty/docs/environment.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Environment variables
|
||||
|
||||
ty defines and respects the following environment variables:
|
||||
|
||||
### `TY_LOG`
|
||||
|
||||
If set, ty will use this value as the log level for its `--verbose` output.
|
||||
Accepts any filter compatible with the `tracing_subscriber` crate.
|
||||
|
||||
For example:
|
||||
|
||||
- `TY_LOG=uv=debug` is the equivalent of `-vv` to the command line
|
||||
- `TY_LOG=trace` will enable all trace-level logging.
|
||||
|
||||
See the [tracing documentation](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax)
|
||||
for more.
|
||||
|
||||
### `TY_LOG_PROFILE`
|
||||
|
||||
If set to `"1"` or `"true"`, ty will enable flamegraph profiling.
|
||||
This creates a `tracing.folded` file that can be used to generate flame graphs
|
||||
for performance analysis.
|
||||
|
||||
### `TY_MAX_PARALLELISM`
|
||||
|
||||
Specifies an upper limit for the number of tasks ty is allowed to run in parallel.
|
||||
|
||||
For example, how many files should be checked in parallel.
|
||||
This isn't the same as a thread limit. ty may spawn additional threads
|
||||
when necessary, e.g. to watch for file system changes or a dedicated UI thread.
|
||||
|
||||
## Externally-defined variables
|
||||
|
||||
ty also reads the following externally defined environment variables:
|
||||
|
||||
### `CONDA_PREFIX`
|
||||
|
||||
Used to detect an activated Conda environment location.
|
||||
If both `VIRTUAL_ENV` and `CONDA_PREFIX` are present, `VIRTUAL_ENV` will be preferred.
|
||||
|
||||
### `RAYON_NUM_THREADS`
|
||||
|
||||
Specifies an upper limit for the number of threads ty uses when performing work in parallel.
|
||||
Equivalent to `TY_MAX_PARALLELISM`.
|
||||
|
||||
This is a standard Rayon environment variable.
|
||||
|
||||
### `VIRTUAL_ENV`
|
||||
|
||||
Used to detect an activated virtual environment.
|
||||
|
||||
### `XDG_CONFIG_HOME`
|
||||
|
||||
Path to user-level configuration directory on Unix systems.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
mod args;
|
||||
mod logging;
|
||||
mod printer;
|
||||
mod python_version;
|
||||
mod version;
|
||||
|
||||
pub use args::Cli;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
use std::io::{self, BufWriter, Write, stdout};
|
||||
use std::fmt::Write;
|
||||
use std::process::{ExitCode, Termination};
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -13,6 +15,7 @@ use std::sync::Mutex;
|
||||
|
||||
use crate::args::{CheckCommand, Command, TerminalColor};
|
||||
use crate::logging::setup_tracing;
|
||||
use crate::printer::Printer;
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
@@ -24,7 +27,7 @@ use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
use ty_project::metadata::options::ProjectOptionsOverrides;
|
||||
use ty_project::watch::ProjectWatcher;
|
||||
use ty_project::{Db, DummyReporter, Reporter, watch};
|
||||
use ty_project::{Db, watch};
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_server::run_server;
|
||||
|
||||
@@ -41,6 +44,8 @@ pub fn run() -> anyhow::Result<ExitStatus> {
|
||||
Command::Check(check_args) => run_check(check_args),
|
||||
Command::Version => version().map(|()| ExitStatus::Success),
|
||||
Command::GenerateShellCompletion { shell } => {
|
||||
use std::io::stdout;
|
||||
|
||||
shell.generate(&mut Cli::command(), &mut stdout());
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
@@ -48,7 +53,7 @@ pub fn run() -> anyhow::Result<ExitStatus> {
|
||||
}
|
||||
|
||||
pub(crate) fn version() -> Result<()> {
|
||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||
let mut stdout = Printer::default().stream_for_requested_summary().lock();
|
||||
let version_info = crate::version::version();
|
||||
writeln!(stdout, "ty {}", &version_info)?;
|
||||
Ok(())
|
||||
@@ -58,9 +63,10 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
set_colored_override(args.color);
|
||||
|
||||
let verbosity = args.verbosity.level();
|
||||
countme::enable(verbosity.is_trace());
|
||||
let _guard = setup_tracing(verbosity, args.color.unwrap_or_default())?;
|
||||
|
||||
let printer = Printer::default().with_verbosity(verbosity);
|
||||
|
||||
tracing::warn!(
|
||||
"ty is pre-release software and not ready for production use. \
|
||||
Expect to encounter bugs, missing features, and fatal errors.",
|
||||
@@ -125,7 +131,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
}
|
||||
|
||||
let project_options_overrides = ProjectOptionsOverrides::new(config_file, options);
|
||||
let (main_loop, main_loop_cancellation_token) = MainLoop::new(project_options_overrides);
|
||||
let (main_loop, main_loop_cancellation_token) =
|
||||
MainLoop::new(project_options_overrides, printer);
|
||||
|
||||
// Listen to Ctrl+C and abort the watch mode.
|
||||
let main_loop_cancellation_token = Mutex::new(Some(main_loop_cancellation_token));
|
||||
@@ -143,16 +150,14 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
main_loop.run(&mut db)?
|
||||
};
|
||||
|
||||
let mut stdout = stdout().lock();
|
||||
match std::env::var("TY_MEMORY_REPORT").as_deref() {
|
||||
let mut stdout = printer.stream_for_requested_summary().lock();
|
||||
match std::env::var(EnvVars::TY_MEMORY_REPORT).as_deref() {
|
||||
Ok("short") => write!(stdout, "{}", db.salsa_memory_dump().display_short())?,
|
||||
Ok("mypy_primer") => write!(stdout, "{}", db.salsa_memory_dump().display_mypy_primer())?,
|
||||
Ok("full") => write!(stdout, "{}", db.salsa_memory_dump().display_full())?,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
tracing::trace!("Counts for entire CLI run:\n{}", countme::get_all());
|
||||
|
||||
std::mem::forget(db);
|
||||
|
||||
if exit_zero {
|
||||
@@ -194,12 +199,16 @@ struct MainLoop {
|
||||
/// The file system watcher, if running in watch mode.
|
||||
watcher: Option<ProjectWatcher>,
|
||||
|
||||
/// Interface for displaying information to the user.
|
||||
printer: Printer,
|
||||
|
||||
project_options_overrides: ProjectOptionsOverrides,
|
||||
}
|
||||
|
||||
impl MainLoop {
|
||||
fn new(
|
||||
project_options_overrides: ProjectOptionsOverrides,
|
||||
printer: Printer,
|
||||
) -> (Self, MainLoopCancellationToken) {
|
||||
let (sender, receiver) = crossbeam_channel::bounded(10);
|
||||
|
||||
@@ -209,6 +218,7 @@ impl MainLoop {
|
||||
receiver,
|
||||
watcher: None,
|
||||
project_options_overrides,
|
||||
printer,
|
||||
},
|
||||
MainLoopCancellationToken { sender },
|
||||
)
|
||||
@@ -225,32 +235,24 @@ impl MainLoop {
|
||||
|
||||
// Do not show progress bars with `--watch`, indicatif does not seem to
|
||||
// handle cancelling independent progress bars very well.
|
||||
self.run_with_progress::<DummyReporter>(db)?;
|
||||
// TODO(zanieb): We can probably use `MultiProgress` to handle this case in the future.
|
||||
self.printer = self.printer.with_no_progress();
|
||||
self.run(db)?;
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
fn run(self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
|
||||
self.run_with_progress::<IndicatifReporter>(db)
|
||||
}
|
||||
|
||||
fn run_with_progress<R>(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
|
||||
where
|
||||
R: Reporter + Default + 'static,
|
||||
{
|
||||
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
|
||||
|
||||
let result = self.main_loop::<R>(db);
|
||||
let result = self.main_loop(db);
|
||||
|
||||
tracing::debug!("Exiting main loop");
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn main_loop<R>(&mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
|
||||
where
|
||||
R: Reporter + Default + 'static,
|
||||
{
|
||||
fn main_loop(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
|
||||
// Schedule the first check.
|
||||
tracing::debug!("Starting main loop");
|
||||
|
||||
@@ -266,7 +268,7 @@ impl MainLoop {
|
||||
// to prevent blocking the main loop here.
|
||||
rayon::spawn(move || {
|
||||
match salsa::Cancelled::catch(|| {
|
||||
let mut reporter = R::default();
|
||||
let mut reporter = IndicatifReporter::from(self.printer);
|
||||
db.check_with_reporter(&mut reporter)
|
||||
}) {
|
||||
Ok(result) => {
|
||||
@@ -301,10 +303,12 @@ impl MainLoop {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let mut stdout = stdout().lock();
|
||||
|
||||
if result.is_empty() {
|
||||
writeln!(stdout, "{}", "All checks passed!".green().bold())?;
|
||||
writeln!(
|
||||
self.printer.stream_for_success_summary(),
|
||||
"{}",
|
||||
"All checks passed!".green().bold()
|
||||
)?;
|
||||
|
||||
if self.watcher.is_none() {
|
||||
return Ok(ExitStatus::Success);
|
||||
@@ -313,14 +317,19 @@ impl MainLoop {
|
||||
let mut max_severity = Severity::Info;
|
||||
let diagnostics_count = result.len();
|
||||
|
||||
let mut stdout = self.printer.stream_for_details().lock();
|
||||
for diagnostic in result {
|
||||
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
|
||||
// Only render diagnostics if they're going to be displayed, since doing
|
||||
// so is expensive.
|
||||
if stdout.is_enabled() {
|
||||
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
|
||||
}
|
||||
|
||||
max_severity = max_severity.max(diagnostic.severity());
|
||||
}
|
||||
|
||||
writeln!(
|
||||
stdout,
|
||||
self.printer.stream_for_failure_summary(),
|
||||
"Found {} diagnostic{}",
|
||||
diagnostics_count,
|
||||
if diagnostics_count > 1 { "s" } else { "" }
|
||||
@@ -352,8 +361,6 @@ impl MainLoop {
|
||||
"Discarding check result for outdated revision: current: {revision}, result revision: {check_revision}"
|
||||
);
|
||||
}
|
||||
|
||||
tracing::trace!("Counts after last check:\n{}", countme::get_all());
|
||||
}
|
||||
|
||||
MainLoopMessage::ApplyChanges(changes) => {
|
||||
@@ -382,27 +389,53 @@ impl MainLoop {
|
||||
}
|
||||
|
||||
/// A progress reporter for `ty check`.
|
||||
#[derive(Default)]
|
||||
struct IndicatifReporter(Option<indicatif::ProgressBar>);
|
||||
enum IndicatifReporter {
|
||||
/// A constructed reporter that is not yet ready, contains the target for the progress bar.
|
||||
Pending(indicatif::ProgressDrawTarget),
|
||||
/// A reporter that is ready, containing a progress bar to report to.
|
||||
///
|
||||
/// Initialization of the bar is deferred to [`ty_project::ProgressReporter::set_files`] so we
|
||||
/// do not initialize the bar too early as it may take a while to collect the number of files to
|
||||
/// process and we don't want to display an empty "0/0" bar.
|
||||
Initialized(indicatif::ProgressBar),
|
||||
}
|
||||
|
||||
impl ty_project::Reporter for IndicatifReporter {
|
||||
impl From<Printer> for IndicatifReporter {
|
||||
fn from(printer: Printer) -> Self {
|
||||
Self::Pending(printer.progress_target())
|
||||
}
|
||||
}
|
||||
|
||||
impl ty_project::ProgressReporter for IndicatifReporter {
|
||||
fn set_files(&mut self, files: usize) {
|
||||
let progress = indicatif::ProgressBar::new(files as u64);
|
||||
progress.set_style(
|
||||
let target = match std::mem::replace(
|
||||
self,
|
||||
IndicatifReporter::Pending(indicatif::ProgressDrawTarget::hidden()),
|
||||
) {
|
||||
Self::Pending(target) => target,
|
||||
Self::Initialized(_) => panic!("The progress reporter should only be initialized once"),
|
||||
};
|
||||
|
||||
let bar = indicatif::ProgressBar::with_draw_target(Some(files as u64), target);
|
||||
bar.set_style(
|
||||
indicatif::ProgressStyle::with_template(
|
||||
"{msg:8.dim} {bar:60.green/dim} {pos}/{len} files",
|
||||
)
|
||||
.unwrap()
|
||||
.progress_chars("--"),
|
||||
);
|
||||
progress.set_message("Checking");
|
||||
|
||||
self.0 = Some(progress);
|
||||
bar.set_message("Checking");
|
||||
*self = Self::Initialized(bar);
|
||||
}
|
||||
|
||||
fn report_file(&self, _file: &ruff_db::files::File) {
|
||||
if let Some(ref progress_bar) = self.0 {
|
||||
progress_bar.inc(1);
|
||||
match self {
|
||||
IndicatifReporter::Initialized(progress_bar) => {
|
||||
progress_bar.inc(1);
|
||||
}
|
||||
IndicatifReporter::Pending(_) => {
|
||||
panic!("`report_file` called before `set_files`")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use tracing_subscriber::filter::LevelFilter;
|
||||
use tracing_subscriber::fmt::format::Writer;
|
||||
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
/// Logging flags to `#[command(flatten)]` into your CLI
|
||||
#[derive(clap::Args, Debug, Clone, Default)]
|
||||
@@ -23,15 +24,32 @@ pub(crate) struct Verbosity {
|
||||
help = "Use verbose output (or `-vv` and `-vvv` for more verbose output)",
|
||||
action = clap::ArgAction::Count,
|
||||
global = true,
|
||||
overrides_with = "quiet",
|
||||
)]
|
||||
verbose: u8,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use quiet output",
|
||||
action = clap::ArgAction::Count,
|
||||
global = true,
|
||||
overrides_with = "verbose",
|
||||
)]
|
||||
quiet: u8,
|
||||
}
|
||||
|
||||
impl Verbosity {
|
||||
/// Returns the verbosity level based on the number of `-v` flags.
|
||||
/// Returns the verbosity level based on the number of `-v` and `-q` flags.
|
||||
///
|
||||
/// Returns `None` if the user did not specify any verbosity flags.
|
||||
pub(crate) fn level(&self) -> VerbosityLevel {
|
||||
// `--quiet` and `--verbose` are mutually exclusive in Clap, so we can just check one first.
|
||||
match self.quiet {
|
||||
0 => {}
|
||||
_ => return VerbosityLevel::Quiet,
|
||||
// TODO(zanieb): Add support for `-qq` with a "silent" mode
|
||||
}
|
||||
|
||||
match self.verbose {
|
||||
0 => VerbosityLevel::Default,
|
||||
1 => VerbosityLevel::Verbose,
|
||||
@@ -41,9 +59,14 @@ impl Verbosity {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
pub(crate) enum VerbosityLevel {
|
||||
/// Quiet output. Only shows Ruff and ty events up to the [`ERROR`](tracing::Level::ERROR).
|
||||
/// Silences output except for summary information.
|
||||
Quiet,
|
||||
|
||||
/// Default output level. Only shows Ruff and ty events up to the [`WARN`](tracing::Level::WARN).
|
||||
#[default]
|
||||
Default,
|
||||
|
||||
/// Enables verbose output. Emits Ruff and ty events up to the [`INFO`](tracing::Level::INFO).
|
||||
@@ -61,6 +84,7 @@ pub(crate) enum VerbosityLevel {
|
||||
impl VerbosityLevel {
|
||||
const fn level_filter(self) -> LevelFilter {
|
||||
match self {
|
||||
VerbosityLevel::Quiet => LevelFilter::ERROR,
|
||||
VerbosityLevel::Default => LevelFilter::WARN,
|
||||
VerbosityLevel::Verbose => LevelFilter::INFO,
|
||||
VerbosityLevel::ExtraVerbose => LevelFilter::DEBUG,
|
||||
@@ -84,7 +108,7 @@ pub(crate) fn setup_tracing(
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
// The `TY_LOG` environment variable overrides the default log level.
|
||||
let filter = if let Ok(log_env_variable) = std::env::var("TY_LOG") {
|
||||
let filter = if let Ok(log_env_variable) = std::env::var(EnvVars::TY_LOG) {
|
||||
EnvFilter::builder()
|
||||
.parse(log_env_variable)
|
||||
.context("Failed to parse directives specified in TY_LOG environment variable.")?
|
||||
@@ -165,7 +189,7 @@ fn setup_profile<S>() -> (
|
||||
where
|
||||
S: Subscriber + for<'span> LookupSpan<'span>,
|
||||
{
|
||||
if let Ok("1" | "true") = std::env::var("TY_LOG_PROFILE").as_deref() {
|
||||
if let Ok("1" | "true") = std::env::var(EnvVars::TY_LOG_PROFILE).as_deref() {
|
||||
let (layer, guard) = tracing_flame::FlameLayer::with_file("tracing.folded")
|
||||
.expect("Flame layer to be created");
|
||||
(Some(layer), Some(guard))
|
||||
|
||||
172
crates/ty/src/printer.rs
Normal file
172
crates/ty/src/printer.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use std::io::StdoutLock;
|
||||
|
||||
use indicatif::ProgressDrawTarget;
|
||||
|
||||
use crate::logging::VerbosityLevel;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub(crate) struct Printer {
|
||||
verbosity: VerbosityLevel,
|
||||
no_progress: bool,
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
#[must_use]
|
||||
pub(crate) fn with_no_progress(self) -> Self {
|
||||
Self {
|
||||
verbosity: self.verbosity,
|
||||
no_progress: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_verbosity(self, verbosity: VerbosityLevel) -> Self {
|
||||
Self {
|
||||
verbosity,
|
||||
no_progress: self.no_progress,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`ProgressDrawTarget`] for this printer.
|
||||
pub(crate) fn progress_target(self) -> ProgressDrawTarget {
|
||||
if self.no_progress {
|
||||
return ProgressDrawTarget::hidden();
|
||||
}
|
||||
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Quiet => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::Default => ProgressDrawTarget::stderr(),
|
||||
// Hide the progress bar when in verbose mode.
|
||||
// Otherwise, it gets interleaved with log messages.
|
||||
VerbosityLevel::Verbose => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::ExtraVerbose => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::Trace => ProgressDrawTarget::hidden(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for important messages.
|
||||
///
|
||||
/// Unlike [`Self::stdout_general`], the returned stream will be enabled when
|
||||
/// [`VerbosityLevel::Quiet`] is used.
|
||||
fn stdout_important(self) -> Stdout {
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Quiet => Stdout::enabled(),
|
||||
VerbosityLevel::Default => Stdout::enabled(),
|
||||
VerbosityLevel::Verbose => Stdout::enabled(),
|
||||
VerbosityLevel::ExtraVerbose => Stdout::enabled(),
|
||||
VerbosityLevel::Trace => Stdout::enabled(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for general messages.
|
||||
///
|
||||
/// The returned stream will be disabled when [`VerbosityLevel::Quiet`] is used.
|
||||
fn stdout_general(self) -> Stdout {
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Quiet => Stdout::disabled(),
|
||||
VerbosityLevel::Default => Stdout::enabled(),
|
||||
VerbosityLevel::Verbose => Stdout::enabled(),
|
||||
VerbosityLevel::ExtraVerbose => Stdout::enabled(),
|
||||
VerbosityLevel::Trace => Stdout::enabled(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for a summary message that was explicitly requested by the
|
||||
/// user.
|
||||
///
|
||||
/// For example, in `ty version` the user has requested the version information and we should
|
||||
/// display it even if [`VerbosityLevel::Quiet`] is used. Or, in `ty check`, if the
|
||||
/// `TY_MEMORY_REPORT` variable has been set, we should display the memory report because the
|
||||
/// user has opted-in to display.
|
||||
pub(crate) fn stream_for_requested_summary(self) -> Stdout {
|
||||
self.stdout_important()
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for a summary message on failure.
|
||||
///
|
||||
/// For example, in `ty check`, this would be used for the message indicating the number of
|
||||
/// diagnostics found. The failure summary should capture information that is not reflected in
|
||||
/// the exit code.
|
||||
pub(crate) fn stream_for_failure_summary(self) -> Stdout {
|
||||
self.stdout_important()
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for a summary message on success.
|
||||
///
|
||||
/// For example, in `ty check`, this would be used for the message indicating that no diagnostic
|
||||
/// were found. The success summary does not capture important information for users that have
|
||||
/// opted-in to [`VerbosityLevel::Quiet`].
|
||||
pub(crate) fn stream_for_success_summary(self) -> Stdout {
|
||||
self.stdout_general()
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for detailed messages.
|
||||
///
|
||||
/// For example, in `ty check`, this would be used for the diagnostic output.
|
||||
pub(crate) fn stream_for_details(self) -> Stdout {
|
||||
self.stdout_general()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum StreamStatus {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Stdout {
|
||||
status: StreamStatus,
|
||||
lock: Option<StdoutLock<'static>>,
|
||||
}
|
||||
|
||||
impl Stdout {
|
||||
fn enabled() -> Self {
|
||||
Self {
|
||||
status: StreamStatus::Enabled,
|
||||
lock: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled() -> Self {
|
||||
Self {
|
||||
status: StreamStatus::Disabled,
|
||||
lock: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lock(mut self) -> Self {
|
||||
match self.status {
|
||||
StreamStatus::Enabled => {
|
||||
// Drop the previous lock first, to avoid deadlocking
|
||||
self.lock.take();
|
||||
self.lock = Some(std::io::stdout().lock());
|
||||
}
|
||||
StreamStatus::Disabled => self.lock = None,
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn handle(&mut self) -> Box<dyn std::io::Write + '_> {
|
||||
match self.lock.as_mut() {
|
||||
Some(lock) => Box::new(lock),
|
||||
None => Box::new(std::io::stdout()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_enabled(&self) -> bool {
|
||||
matches!(self.status, StreamStatus::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Write for Stdout {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
match self.status {
|
||||
StreamStatus::Enabled => {
|
||||
let _ = write!(self.handle(), "{s}");
|
||||
Ok(())
|
||||
}
|
||||
StreamStatus::Disabled => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,64 @@ use std::{
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_quiet_output() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_file("test.py", "x: int = 1")?;
|
||||
|
||||
// By default, we emit an "all checks passed" message
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
");
|
||||
|
||||
// With `quiet`, the message is not displayed
|
||||
assert_cmd_snapshot!(case.command().arg("--quiet"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let case = CliTest::with_file("test.py", "x: int = 'foo'")?;
|
||||
|
||||
// By default, we emit a diagnostic
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-assignment]: Object of type `Literal["foo"]` is not assignable to `int`
|
||||
--> test.py:1:1
|
||||
|
|
||||
1 | x: int = 'foo'
|
||||
| ^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"#);
|
||||
|
||||
// With `quiet`, the diagnostic is not displayed, just the summary message
|
||||
assert_cmd_snapshot!(case.command().arg("--quiet"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_in_sub_directory() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([("test.py", "~"), ("subdir/nothing", "")])?;
|
||||
|
||||
@@ -10,7 +10,7 @@ use ty_python_semantic::{Completion, NameKind, SemanticModel};
|
||||
use crate::Db;
|
||||
use crate::find_node::covering_node;
|
||||
|
||||
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion> {
|
||||
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion<'_>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
|
||||
let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else {
|
||||
@@ -1223,33 +1223,33 @@ quux.<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||
bar
|
||||
baz
|
||||
foo
|
||||
__annotations__
|
||||
__class__
|
||||
__delattr__
|
||||
__dict__
|
||||
__dir__
|
||||
__doc__
|
||||
__eq__
|
||||
__format__
|
||||
__getattribute__
|
||||
__getstate__
|
||||
__hash__
|
||||
__init__
|
||||
__init_subclass__
|
||||
__module__
|
||||
__ne__
|
||||
__new__
|
||||
__reduce__
|
||||
__reduce_ex__
|
||||
__repr__
|
||||
__setattr__
|
||||
__sizeof__
|
||||
__str__
|
||||
__subclasshook__
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
bar :: Unknown | Literal[2]
|
||||
baz :: Unknown | Literal[3]
|
||||
foo :: Unknown | Literal[1]
|
||||
__annotations__ :: dict[str, Any]
|
||||
__class__ :: type
|
||||
__delattr__ :: bound method object.__delattr__(name: str, /) -> None
|
||||
__dict__ :: dict[str, Any]
|
||||
__dir__ :: bound method object.__dir__() -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: bound method object.__eq__(value: object, /) -> bool
|
||||
__format__ :: bound method object.__format__(format_spec: str, /) -> str
|
||||
__getattribute__ :: bound method object.__getattribute__(name: str, /) -> Any
|
||||
__getstate__ :: bound method object.__getstate__() -> object
|
||||
__hash__ :: bound method object.__hash__() -> int
|
||||
__init__ :: bound method Quux.__init__() -> Unknown
|
||||
__init_subclass__ :: bound method object.__init_subclass__() -> None
|
||||
__module__ :: str
|
||||
__ne__ :: bound method object.__ne__(value: object, /) -> bool
|
||||
__new__ :: bound method object.__new__() -> Self
|
||||
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: bound method object.__repr__() -> str
|
||||
__setattr__ :: bound method object.__setattr__(name: str, value: Any, /) -> None
|
||||
__sizeof__ :: bound method object.__sizeof__() -> int
|
||||
__str__ :: bound method object.__str__() -> str
|
||||
__subclasshook__ :: bound method type.__subclasshook__(subclass: type, /) -> bool
|
||||
");
|
||||
}
|
||||
|
||||
@@ -1268,33 +1268,33 @@ quux.b<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||
bar
|
||||
baz
|
||||
foo
|
||||
__annotations__
|
||||
__class__
|
||||
__delattr__
|
||||
__dict__
|
||||
__dir__
|
||||
__doc__
|
||||
__eq__
|
||||
__format__
|
||||
__getattribute__
|
||||
__getstate__
|
||||
__hash__
|
||||
__init__
|
||||
__init_subclass__
|
||||
__module__
|
||||
__ne__
|
||||
__new__
|
||||
__reduce__
|
||||
__reduce_ex__
|
||||
__repr__
|
||||
__setattr__
|
||||
__sizeof__
|
||||
__str__
|
||||
__subclasshook__
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
bar :: Unknown | Literal[2]
|
||||
baz :: Unknown | Literal[3]
|
||||
foo :: Unknown | Literal[1]
|
||||
__annotations__ :: dict[str, Any]
|
||||
__class__ :: type
|
||||
__delattr__ :: bound method object.__delattr__(name: str, /) -> None
|
||||
__dict__ :: dict[str, Any]
|
||||
__dir__ :: bound method object.__dir__() -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: bound method object.__eq__(value: object, /) -> bool
|
||||
__format__ :: bound method object.__format__(format_spec: str, /) -> str
|
||||
__getattribute__ :: bound method object.__getattribute__(name: str, /) -> Any
|
||||
__getstate__ :: bound method object.__getstate__() -> object
|
||||
__hash__ :: bound method object.__hash__() -> int
|
||||
__init__ :: bound method Quux.__init__() -> Unknown
|
||||
__init_subclass__ :: bound method object.__init_subclass__() -> None
|
||||
__module__ :: str
|
||||
__ne__ :: bound method object.__ne__(value: object, /) -> bool
|
||||
__new__ :: bound method object.__new__() -> Self
|
||||
__reduce__ :: bound method object.__reduce__() -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: bound method object.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: bound method object.__repr__() -> str
|
||||
__setattr__ :: bound method object.__setattr__(name: str, value: Any, /) -> None
|
||||
__sizeof__ :: bound method object.__sizeof__() -> int
|
||||
__str__ :: bound method object.__str__() -> str
|
||||
__subclasshook__ :: bound method type.__subclasshook__(subclass: type, /) -> bool
|
||||
");
|
||||
}
|
||||
|
||||
@@ -1321,6 +1321,89 @@ class Quux:
|
||||
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_attributes1() {
|
||||
let test = cursor_test(
|
||||
"\
|
||||
class Quux:
|
||||
some_attribute: int = 1
|
||||
|
||||
def __init__(self):
|
||||
self.foo = 1
|
||||
self.bar = 2
|
||||
self.baz = 3
|
||||
|
||||
def some_method(self) -> int:
|
||||
return 1
|
||||
|
||||
@property
|
||||
def some_property(self) -> int:
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def some_class_method(self) -> int:
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def some_static_method(self) -> int:
|
||||
return 1
|
||||
|
||||
Quux.<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||
mro :: def mro(self) -> list[type]
|
||||
some_attribute :: int
|
||||
some_class_method :: bound method <class 'Quux'>.some_class_method() -> int
|
||||
some_method :: def some_method(self) -> int
|
||||
some_property :: property
|
||||
some_static_method :: def some_static_method(self) -> int
|
||||
__annotations__ :: dict[str, Any]
|
||||
__base__ :: type | None
|
||||
__bases__ :: tuple[type, ...]
|
||||
__basicsize__ :: int
|
||||
__call__ :: def __call__(self, *args: Any, **kwds: Any) -> Any
|
||||
__class__ :: <class 'type'>
|
||||
__delattr__ :: def __delattr__(self, name: str, /) -> None
|
||||
__dict__ :: MappingProxyType[str, Any]
|
||||
__dictoffset__ :: int
|
||||
__dir__ :: def __dir__(self) -> Iterable[str]
|
||||
__doc__ :: str | None
|
||||
__eq__ :: def __eq__(self, value: object, /) -> bool
|
||||
__flags__ :: int
|
||||
__format__ :: def __format__(self, format_spec: str, /) -> str
|
||||
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
|
||||
__getstate__ :: def __getstate__(self) -> object
|
||||
__hash__ :: def __hash__(self) -> int
|
||||
__init__ :: def __init__(self) -> Unknown
|
||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
||||
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
||||
__itemsize__ :: int
|
||||
__module__ :: str
|
||||
__mro__ :: tuple[<class 'type'>, <class 'object'>]
|
||||
__name__ :: str
|
||||
__ne__ :: def __ne__(self, value: object, /) -> bool
|
||||
__new__ :: def __new__(cls) -> Self
|
||||
__or__ :: def __or__(self, value: Any, /) -> UnionType
|
||||
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
|
||||
__qualname__ :: str
|
||||
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
|
||||
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||
__repr__ :: def __repr__(self) -> str
|
||||
__ror__ :: def __ror__(self, value: Any, /) -> UnionType
|
||||
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
|
||||
__sizeof__ :: def __sizeof__(self) -> int
|
||||
__str__ :: def __str__(self) -> str
|
||||
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
|
||||
__subclasses__ :: def __subclasses__(self: Self) -> list[Self]
|
||||
__subclasshook__ :: bound method <class 'object'>.__subclasshook__(subclass: type, /) -> bool
|
||||
__text_signature__ :: str | None
|
||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||
__weakrefoffset__ :: int
|
||||
");
|
||||
}
|
||||
|
||||
// We don't yet take function parameters into account.
|
||||
#[test]
|
||||
fn call_prefix1() {
|
||||
@@ -2366,7 +2449,22 @@ importlib.<CURSOR>
|
||||
self.completions_if(|c| !c.builtin)
|
||||
}
|
||||
|
||||
fn completions_without_builtins_with_types(&self) -> String {
|
||||
self.completions_if_snapshot(
|
||||
|c| !c.builtin,
|
||||
|c| format!("{} :: {}", c.name, c.ty.display(&self.db)),
|
||||
)
|
||||
}
|
||||
|
||||
fn completions_if(&self, predicate: impl Fn(&Completion) -> bool) -> String {
|
||||
self.completions_if_snapshot(predicate, |c| c.name.as_str().to_string())
|
||||
}
|
||||
|
||||
fn completions_if_snapshot(
|
||||
&self,
|
||||
predicate: impl Fn(&Completion) -> bool,
|
||||
snapshot: impl Fn(&Completion) -> String,
|
||||
) -> String {
|
||||
let completions = completion(&self.db, self.cursor.file, self.cursor.offset);
|
||||
if completions.is_empty() {
|
||||
return "<No completions found>".to_string();
|
||||
@@ -2374,7 +2472,7 @@ importlib.<CURSOR>
|
||||
let included = completions
|
||||
.iter()
|
||||
.filter(|label| predicate(label))
|
||||
.map(|completion| completion.name.as_str().to_string())
|
||||
.map(snapshot)
|
||||
.collect::<Vec<String>>();
|
||||
if included.is_empty() {
|
||||
// It'd be nice to include the actual number of
|
||||
|
||||
@@ -242,22 +242,6 @@ mod tests {
|
||||
}
|
||||
|
||||
pub(super) fn render_diagnostics<I, D>(&self, diagnostics: I) -> String
|
||||
where
|
||||
I: IntoIterator<Item = D>,
|
||||
D: IntoDiagnostic,
|
||||
{
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.color(false)
|
||||
.format(DiagnosticFormat::Full);
|
||||
|
||||
self.render_diagnostics_with_config(diagnostics, &config)
|
||||
}
|
||||
|
||||
pub(super) fn render_diagnostics_with_config<I, D>(
|
||||
&self,
|
||||
diagnostics: I,
|
||||
config: &DisplayDiagnosticConfig,
|
||||
) -> String
|
||||
where
|
||||
I: IntoIterator<Item = D>,
|
||||
D: IntoDiagnostic,
|
||||
@@ -266,9 +250,12 @@ mod tests {
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
let config = DisplayDiagnosticConfig::default()
|
||||
.color(false)
|
||||
.format(DiagnosticFormat::Full);
|
||||
for diagnostic in diagnostics {
|
||||
let diag = diagnostic.into_diagnostic();
|
||||
write!(buf, "{}", diag.display(&self.db, config)).unwrap();
|
||||
write!(buf, "{}", diag.display(&self.db, &config)).unwrap();
|
||||
}
|
||||
|
||||
buf
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ use std::{cmp, fmt};
|
||||
|
||||
use crate::metadata::settings::file_settings;
|
||||
use crate::{DEFAULT_LINT_REGISTRY, DummyReporter};
|
||||
use crate::{Project, ProjectMetadata, Reporter};
|
||||
use crate::{ProgressReporter, Project, ProjectMetadata};
|
||||
use ruff_db::Db as SourceDb;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{File, Files};
|
||||
@@ -87,7 +87,7 @@ impl ProjectDatabase {
|
||||
}
|
||||
|
||||
/// Checks all open files in the project and its dependencies, using the given reporter.
|
||||
pub fn check_with_reporter(&self, reporter: &mut dyn Reporter) -> Vec<Diagnostic> {
|
||||
pub fn check_with_reporter(&self, reporter: &mut dyn ProgressReporter) -> Vec<Diagnostic> {
|
||||
let reporter = AssertUnwindSafe(reporter);
|
||||
self.project().check(self, CheckMode::OpenFiles, reporter)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ impl ProjectDatabase {
|
||||
/// Check the project with the given mode.
|
||||
pub fn check_with_mode(&self, mode: CheckMode) -> Vec<Diagnostic> {
|
||||
let mut reporter = DummyReporter;
|
||||
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter);
|
||||
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn ProgressReporter);
|
||||
self.project().check(self, mode, reporter)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user