Compare commits

..

4 Commits

Author SHA1 Message Date
David Peter
7d3ad59970 Merge remote-tracking branch 'origin/main' into david/dataclass-final-fields 2025-07-08 14:35:54 +02:00
David Peter
d133d7ab03 Add comment 2025-07-08 14:30:30 +02:00
David Peter
7d642e6416 Add test for 'Final'-qualified field without a default 2025-07-08 14:25:19 +02:00
David Peter
b92a283f35 [ty] Add tests for dataclass fields annotated with Final 2025-07-08 13:23:19 +02:00
133 changed files with 1454 additions and 6214 deletions

View File

@@ -385,8 +385,8 @@ jobs:
- name: "Build"
run: cargo build --release --locked
cargo-test-msrv:
name: "cargo test (msrv)"
cargo-build-msrv:
name: "cargo build (msrv)"
runs-on: depot-ubuntu-latest-8
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}

View File

@@ -12,7 +12,6 @@ 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 }}

View File

@@ -17,7 +17,6 @@ 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:
@@ -64,75 +63,32 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@f0eec0e549684d8e1d7b8bc3e351202124b63bda"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@9c34dc514ee9aef6735db1dfebb80f63acbc3440"
ecosystem-analyzer \
--repository ruff \
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
analyze \
--projects ruff/projects_old.txt \
--commit old_commit \
--output diagnostics_old.json
mkdir dist
ecosystem-analyzer \
--repository ruff \
analyze \
--projects ruff/projects_new.txt \
--commit new_commit \
--output diagnostics_new.json
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 dist/diff.html
--output-html diff.html
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
- name: Upload HTML diff report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: diff.html
path: dist/diff.html
path: diff.html

View File

@@ -1,85 +0,0 @@
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
View File

@@ -10,7 +10,6 @@ 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...

View File

@@ -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|environment).md|
crates/ty/docs/(configuration|rules|cli).md|
crates/ruff_benchmark/resources/.*|
crates/ruff_linter/resources/.*|
crates/ruff_linter/src/rules/.*/snapshots/.*|

76
Cargo.lock generated
View File

@@ -680,6 +680,11 @@ 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"
@@ -847,6 +852,19 @@ 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"
@@ -2244,7 +2262,7 @@ dependencies = [
"once_cell",
"pep440_rs",
"regex",
"rustc-hash",
"rustc-hash 2.1.1",
"serde",
"smallvec",
"thiserror 1.0.69",
@@ -2754,7 +2772,7 @@ dependencies = [
"ruff_source_file",
"ruff_text_size",
"ruff_workspace",
"rustc-hash",
"rustc-hash 2.1.1",
"serde",
"serde_json",
"shellexpand",
@@ -2800,7 +2818,7 @@ dependencies = [
"ruff_python_formatter",
"ruff_python_parser",
"ruff_python_trivia",
"rustc-hash",
"rustc-hash 2.1.1",
"serde",
"serde_json",
"tikv-jemallocator",
@@ -2829,7 +2847,7 @@ dependencies = [
"arc-swap",
"camino",
"countme",
"dashmap",
"dashmap 6.1.0",
"dunce",
"etcetera",
"filetime",
@@ -2848,7 +2866,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa",
"schemars",
"serde",
@@ -2856,7 +2874,6 @@ dependencies = [
"thiserror 2.0.12",
"tracing",
"tracing-subscriber",
"ty_static",
"web-time",
"zip",
]
@@ -2900,7 +2917,6 @@ dependencies = [
"tracing-subscriber",
"ty",
"ty_project",
"ty_static",
"url",
]
@@ -2922,7 +2938,7 @@ dependencies = [
"ruff_cache",
"ruff_macros",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"schemars",
"serde",
"static_assertions",
@@ -3004,7 +3020,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"schemars",
"serde",
"serde_json",
@@ -3076,7 +3092,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa",
"schemars",
"serde",
@@ -3126,7 +3142,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa",
"schemars",
"serde",
@@ -3175,7 +3191,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"serde",
"serde_json",
"static_assertions",
@@ -3199,7 +3215,7 @@ dependencies = [
"ruff_python_parser",
"ruff_python_stdlib",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"schemars",
"serde",
"smallvec",
@@ -3260,7 +3276,7 @@ dependencies = [
"ruff_source_file",
"ruff_text_size",
"ruff_workspace",
"rustc-hash",
"rustc-hash 2.1.1",
"serde",
"serde_json",
"shellexpand",
@@ -3349,7 +3365,7 @@ dependencies = [
"ruff_python_semantic",
"ruff_python_stdlib",
"ruff_source_file",
"rustc-hash",
"rustc-hash 2.1.1",
"schemars",
"serde",
"shellexpand",
@@ -3368,6 +3384,12 @@ 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"
@@ -3422,7 +3444,7 @@ dependencies = [
"parking_lot",
"portable-atomic",
"rayon",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa-macro-rules",
"salsa-macros",
"smallvec",
@@ -4120,6 +4142,7 @@ dependencies = [
"clap",
"clap_complete_command",
"colored 3.0.0",
"countme",
"crossbeam",
"ctrlc",
"dunce",
@@ -4142,7 +4165,6 @@ dependencies = [
"ty_project",
"ty_python_semantic",
"ty_server",
"ty_static",
"wild",
]
@@ -4156,7 +4178,7 @@ dependencies = [
"ruff_python_ast",
"ruff_python_parser",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa",
"smallvec",
"tracing",
@@ -4188,7 +4210,7 @@ dependencies = [
"ruff_python_ast",
"ruff_python_formatter",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa",
"schemars",
"serde",
@@ -4209,6 +4231,7 @@ dependencies = [
"camino",
"colored 3.0.0",
"compact_str",
"countme",
"dir-test",
"drop_bomb",
"get-size2",
@@ -4232,7 +4255,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa",
"schemars",
"serde",
@@ -4245,7 +4268,6 @@ dependencies = [
"thiserror 2.0.12",
"tracing",
"ty_python_semantic",
"ty_static",
"ty_test",
"ty_vendored",
]
@@ -4264,7 +4286,7 @@ dependencies = [
"ruff_notebook",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"salsa",
"serde",
"serde_json",
@@ -4277,13 +4299,6 @@ dependencies = [
"ty_vendored",
]
[[package]]
name = "ty_static"
version = "0.0.1"
dependencies = [
"ruff_macros",
]
[[package]]
name = "ty_test"
version = "0.0.0"
@@ -4302,7 +4317,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash",
"rustc-hash 2.1.1",
"rustc-stable-hash",
"salsa",
"serde",
@@ -4312,7 +4327,6 @@ dependencies = [
"toml",
"tracing",
"ty_python_semantic",
"ty_static",
"ty_vendored",
]

View File

@@ -44,7 +44,6 @@ 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" }
@@ -84,7 +83,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" }
@@ -174,7 +173,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" }
@@ -184,7 +183,11 @@ 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" }
@@ -219,8 +222,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

View File

@@ -430,7 +430,6 @@ 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/))

View File

@@ -681,7 +681,7 @@ mod tests {
UnsafeFixes::Enabled,
)
.unwrap();
if diagnostics.inner.iter().any(Diagnostic::is_invalid_syntax) {
if diagnostics.inner.iter().any(Diagnostic::is_syntax_error) {
parse_errors.push(path.clone());
}
paths.push(path);

View File

@@ -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, Violation, fs, warn_user_once};
use ruff_linter::{IOError, fs, warn_user_once};
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::TextRange;
use ruff_workspace::resolver::{
@@ -129,7 +129,11 @@ pub(crate) fn check(
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
Diagnostics::new(
vec![IOError { message }.into_diagnostic(TextRange::default(), &dummy)],
vec![diagnostic_from_violation(
IOError { message },
TextRange::default(),
&dummy,
)],
FxHashMap::default(),
)
} else {
@@ -162,9 +166,7 @@ pub(crate) fn check(
|a, b| (a.0 + b.0, a.1 + b.1),
);
all_diagnostics
.inner
.sort_by(Diagnostic::ruff_start_ordering);
all_diagnostics.inner.sort();
// Store the caches.
caches.persist()?;

View File

@@ -1,7 +1,6 @@
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;
@@ -53,8 +52,6 @@ pub(crate) fn check_stdin(
noqa,
fix_mode,
)?;
diagnostics
.inner
.sort_unstable_by(Diagnostic::ruff_start_ordering);
diagnostics.inner.sort_unstable();
Ok(diagnostics)
}

View File

@@ -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;
use ruff_linter::message::{create_syntax_error_diagnostic, diagnostic_from_violation};
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, Violation, fs};
use ruff_linter::{IOError, fs};
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
use ruff_source_file::SourceFileBuilder;
@@ -62,12 +62,13 @@ impl Diagnostics {
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
let source_file = SourceFileBuilder::new(name, "").finish();
Self::new(
vec![
vec![diagnostic_from_violation(
IOError {
message: err.to_string(),
}
.into_diagnostic(TextRange::default(), &source_file),
],
},
TextRange::default(),
&source_file,
)],
FxHashMap::default(),
)
} else {

View File

@@ -20,7 +20,6 @@ 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 }

View File

@@ -83,7 +83,7 @@ impl Diagnostic {
///
/// Note that `message` is stored in the primary annotation, _not_ in the primary diagnostic
/// message.
pub fn invalid_syntax(
pub fn syntax_error(
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_invalid_syntax(&self) -> bool {
pub fn is_syntax_error(&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_invalid_syntax() {
if self.is_syntax_error() {
None
} else {
Some(format!(
@@ -447,16 +447,20 @@ impl Diagnostic {
pub fn expect_range(&self) -> TextRange {
self.range().expect("Expected a range for the primary span")
}
}
/// 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(),
))
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())),
)
}
}

View File

@@ -5,7 +5,6 @@ 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;
@@ -51,8 +50,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(EnvVars::TY_MAX_PARALLELISM)
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))
std::env::var("TY_MAX_PARALLELISM")
.or_else(|_| std::env::var("RAYON_NUM_THREADS"))
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| {

View File

@@ -13,7 +13,6 @@ 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"] }

View File

@@ -4,7 +4,7 @@ use anyhow::Result;
use crate::{
generate_cli_help, generate_docs, generate_json_schema, generate_ty_cli_reference,
generate_ty_env_vars_reference, generate_ty_options, generate_ty_rules, generate_ty_schema,
generate_ty_options, generate_ty_rules, generate_ty_schema,
};
pub(crate) const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all";
@@ -44,8 +44,5 @@ 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(())
}

View File

@@ -1,119 +0,0 @@
//! 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(&current, &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")
}

View File

@@ -18,7 +18,6 @@ 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;
@@ -54,8 +53,6 @@ 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.
@@ -101,7 +98,6 @@ 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)?,

View File

@@ -1,10 +1,10 @@
"""
Should emit:
B017 - on lines 24, 28, 46, 49, 52, and 58
B017 - on lines 23 and 41
"""
import asyncio
import unittest
import pytest, contextlib
import pytest
CONSTANT = True

View File

@@ -1,28 +0,0 @@
"""
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")

View File

@@ -125,19 +125,3 @@ 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

View File

@@ -7,9 +7,7 @@ use ruff_python_semantic::analyze::typing;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::preview::{
is_assert_raises_exception_call_enabled, is_optional_as_none_in_union_enabled,
};
use crate::preview::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,
@@ -1039,14 +1037,27 @@ 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,
@@ -1057,66 +1068,21 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
]) {
flake8_use_pathlib::rules::replaceable_by_pathlib(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::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 checker.is_rule_enabled(Rule::OsSepSplit) {
flake8_use_pathlib::rules::os_sep_split(checker, call);
}
@@ -1270,11 +1236,6 @@ 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(&[

View File

@@ -64,6 +64,7 @@ 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;
@@ -3157,7 +3158,7 @@ impl<'a> LintContext<'a> {
) -> DiagnosticGuard<'chk, 'a> {
DiagnosticGuard {
context: self,
diagnostic: Some(kind.into_diagnostic(range, &self.source_file)),
diagnostic: Some(diagnostic_from_violation(kind, range, &self.source_file)),
rule: T::rule(),
}
}
@@ -3176,7 +3177,7 @@ impl<'a> LintContext<'a> {
if self.is_rule_enabled(rule) {
Some(DiagnosticGuard {
context: self,
diagnostic: Some(kind.into_diagnostic(range, &self.source_file)),
diagnostic: Some(diagnostic_from_violation(kind, range, &self.source_file)),
rule,
})
} else {

View File

@@ -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::rules::OsPathAbspath),
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::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::rules::OsRmdir),
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRemove),
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsUnlink),
(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, "109") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsGetcwd),
(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, "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, "116") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsStat),
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsabs),
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsabs),
(Flake8UsePathlib, "118") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathJoin),
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathBasename),
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathDirname),
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathBasename),
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::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),

View File

@@ -618,7 +618,8 @@ mod tests {
use crate::fix::edits::{
add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon,
};
use crate::{Edit, Fix, Locator, Violation};
use crate::message::diagnostic_from_violation;
use crate::{Edit, Fix, Locator};
/// Parse the given source using [`Mode::Module`] and return the first statement.
fn parse_first_stmt(source: &str) -> Result<Stmt> {
@@ -749,8 +750,8 @@ x = 1 \
let diag = {
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
let mut iter = edits.into_iter();
// The choice of rule here is arbitrary.
let mut diagnostic = MissingNewlineAtEndOfFile.into_diagnostic(
let mut diagnostic = diagnostic_from_violation(
MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary.
TextRange::default(),
&SourceFileBuilder::new("<filename>", "<code>").finish(),
);

View File

@@ -172,10 +172,11 @@ 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(
@@ -186,7 +187,8 @@ mod tests {
edit.into_iter()
.map(|edit| {
// The choice of rule here is arbitrary.
let mut diagnostic = MissingNewlineAtEndOfFile.into_diagnostic(
let mut diagnostic = diagnostic_from_violation(
MissingNewlineAtEndOfFile,
edit.range(),
&SourceFileBuilder::new(filename, source).finish(),
);

View File

@@ -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_invalid_syntax),
has_no_syntax_errors: !diagnostics.iter().any(Diagnostic::is_syntax_error),
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_invalid_syntax);
has_no_syntax_errors = !diagnostics.iter().any(Diagnostic::is_syntax_error);
} else {
// If the source code had no syntax errors on the first pass, but
// does on a subsequent pass, then we've introduced a

View File

@@ -24,6 +24,7 @@ pub use sarif::SarifEmitter;
pub use text::TextEmitter;
use crate::Fix;
use crate::Violation;
use crate::registry::Rule;
mod azure;
@@ -107,6 +108,28 @@ 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,

View File

@@ -1225,6 +1225,8 @@ 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,
@@ -1232,7 +1234,6 @@ 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(
@@ -2831,10 +2832,10 @@ mod tests {
assert_eq!(output, format!("{contents}"));
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
let messages = [UnusedVariable {
name: "x".to_string(),
}
.into_diagnostic(
let messages = [diagnostic_from_violation(
UnusedVariable {
name: "x".to_string(),
},
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
)];
@@ -2855,14 +2856,15 @@ mod tests {
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
let messages = [
AmbiguousVariableName("x".to_string()).into_diagnostic(
diagnostic_from_violation(
AmbiguousVariableName("x".to_string()),
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
),
UnusedVariable {
name: "x".to_string(),
}
.into_diagnostic(
diagnostic_from_violation(
UnusedVariable {
name: "x".to_string(),
},
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
),
@@ -2885,14 +2887,15 @@ mod tests {
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
let messages = [
AmbiguousVariableName("x".to_string()).into_diagnostic(
diagnostic_from_violation(
AmbiguousVariableName("x".to_string()),
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
),
UnusedVariable {
name: "x".to_string(),
}
.into_diagnostic(
diagnostic_from_violation(
UnusedVariable {
name: "x".to_string(),
},
TextRange::new(TextSize::from(0), TextSize::from(0)),
&source_file,
),
@@ -2928,8 +2931,11 @@ 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 = [PrintfStringFormatting
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
let messages = [diagnostic_from_violation(
PrintfStringFormatting,
TextRange::new(12.into(), 79.into()),
&source_file,
)];
let comment_ranges = CommentRanges::default();
let edits = generate_noqa_edits(
path,
@@ -2958,8 +2964,11 @@ foo;
bar =
";
let source_file = SourceFileBuilder::new(path.to_string_lossy(), source).finish();
let messages =
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
let messages = [diagnostic_from_violation(
UselessSemicolon,
TextRange::new(4.into(), 5.into()),
&source_file,
)];
let noqa_line_for = NoqaMapping::default();
let comment_ranges = CommentRanges::default();
let edits = generate_noqa_edits(

View File

@@ -69,71 +69,6 @@ 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 {
@@ -190,8 +125,3 @@ 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()
}

View File

@@ -6,10 +6,11 @@ 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> {
@@ -29,8 +30,11 @@ pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings)
source_file.name(),
);
if settings.rules.enabled(Rule::IOError) {
let diagnostic =
IOError { message }.into_diagnostic(TextRange::default(), source_file);
let diagnostic = diagnostic_from_violation(
IOError { message },
TextRange::default(),
source_file,
);
messages.push(diagnostic);
} else {
warn!(
@@ -52,8 +56,11 @@ 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 =
InvalidPyprojectToml { message: toml_err }.into_diagnostic(range, source_file);
let diagnostic = diagnostic_from_violation(
InvalidPyprojectToml { message: toml_err },
range,
source_file,
);
messages.push(diagnostic);
}

View File

@@ -283,7 +283,7 @@ impl Violation for SuspiciousXmlrpcImport {
///
/// ## Example
/// ```python
/// from wsgiref.handlers import CGIHandler
/// import wsgiref.handlers.CGIHandler
/// ```
///
/// ## References

View File

@@ -16,14 +16,11 @@ 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_0.py"))]
#[test_case(Rule::AssertRaisesException, Path::new("B017_1.py"))]
#[test_case(Rule::AssertRaisesException, Path::new("B017.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"))]
@@ -177,23 +174,4 @@ 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(())
}
}

View File

@@ -1,7 +1,7 @@
use std::fmt;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Arguments, Expr, WithItem};
use ruff_python_ast::{self as ast, Expr, WithItem};
use ruff_text_size::Ranged;
use crate::Violation;
@@ -56,48 +56,6 @@ 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 {
@@ -115,31 +73,33 @@ pub(crate) fn assert_raises_exception(checker: &Checker, items: &[WithItem]) {
continue;
}
if let Some(exception) =
detect_blind_exception(checker.semantic(), func.as_ref(), arguments)
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())
{
checker.report_diagnostic(AssertRaisesException { exception }, item.range());
continue;
}
}
}
/// 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);
checker.report_diagnostic(AssertRaisesException { exception }, item.range());
}
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---
B017_0.py:23:14: B017 Do not assert blind exception: `Exception`
B017.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_0.py:23:14: B017 Do not assert blind exception: `Exception`
24 | raise Exception("Evil I say!")
|
B017_0.py:27:14: B017 Do not assert blind exception: `BaseException`
B017.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_0.py:27:14: B017 Do not assert blind exception: `BaseException`
28 | raise Exception("Evil I say!")
|
B017_0.py:45:10: B017 Do not assert blind exception: `Exception`
B017.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_0.py:45:10: B017 Do not assert blind exception: `Exception`
46 | raise ValueError("Hello")
|
B017_0.py:48:10: B017 Do not assert blind exception: `Exception`
B017.py:48:10: B017 Do not assert blind exception: `Exception`
|
46 | raise ValueError("Hello")
47 |
@@ -35,7 +35,7 @@ B017_0.py:48:10: B017 Do not assert blind exception: `Exception`
49 | raise ValueError("Hello")
|
B017_0.py:57:36: B017 Do not assert blind exception: `Exception`
B017.py:57:36: B017 Do not assert blind exception: `Exception`
|
55 | raise ValueError("This is also fine")
56 |

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
---

View File

@@ -1,45 +0,0 @@
---
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")
|

View File

@@ -1,37 +0,0 @@
---
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")
|

View File

@@ -1,17 +1,10 @@
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_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 {
pub(crate) fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
expr.as_call_expr().is_some_and(|expr_call| {
checker
.semantic()
@@ -20,22 +13,27 @@ pub(crate) fn is_pathlib_path_call(checker: &Checker, expr: &Expr) -> bool {
})
}
/// 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(
pub(crate) fn check_os_path_get_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(fn_argument, 0) else {
let Some(arg) = call.arguments.find_argument_value("filename", 0) else {
return;
};
@@ -58,10 +56,10 @@ pub(crate) fn check_os_pathlib_single_arg_calls(
Applicability::Safe
};
let replacement = if is_pathlib_path_call(checker, arg) {
format!("{arg_code}.{attr}")
let replacement = if is_path_call(checker, arg) {
format!("{arg_code}.stat().{attr}")
} else {
format!("{binding}({arg_code}).{attr}")
format!("{binding}({arg_code}).stat().{attr}")
};
Ok(Fix::applicable_edits(

View File

@@ -80,48 +80,6 @@ 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"))]

View File

@@ -1,45 +1,19 @@
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;

View File

@@ -1,74 +0,0 @@
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,
);
}

View File

@@ -1,73 +0,0 @@
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,
);
}

View File

@@ -1,73 +0,0 @@
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,
);
}

View File

@@ -1,73 +0,0 @@
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,
);
}

View File

@@ -1,73 +0,0 @@
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,
);
}

View File

@@ -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_pathlib_single_arg_calls;
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -61,15 +61,12 @@ impl Violation for OsPathGetatime {
}
/// PTH203
pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "path", "getatime"] {
return;
}
check_os_pathlib_single_arg_calls(
pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall) {
check_os_path_get_calls(
checker,
call,
"stat().st_atime",
"filename",
"getatime",
"st_atime",
is_fix_os_path_getatime_enabled(checker.settings()),
OsPathGetatime,
);

View File

@@ -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_pathlib_single_arg_calls;
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -62,15 +62,12 @@ impl Violation for OsPathGetctime {
}
/// PTH205
pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "path", "getctime"] {
return;
}
check_os_pathlib_single_arg_calls(
pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall) {
check_os_path_get_calls(
checker,
call,
"stat().st_ctime",
"filename",
"getctime",
"st_ctime",
is_fix_os_path_getctime_enabled(checker.settings()),
OsPathGetctime,
);

View File

@@ -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_pathlib_single_arg_calls;
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -62,15 +62,12 @@ impl Violation for OsPathGetmtime {
}
/// PTH204
pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "path", "getmtime"] {
return;
}
check_os_pathlib_single_arg_calls(
pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall) {
check_os_path_get_calls(
checker,
call,
"stat().st_mtime",
"filename",
"getmtime",
"st_mtime",
is_fix_os_path_getmtime_enabled(checker.settings()),
OsPathGetmtime,
);

View File

@@ -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_pathlib_single_arg_calls;
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
@@ -62,15 +62,12 @@ impl Violation for OsPathGetsize {
}
/// PTH202
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "path", "getsize"] {
return;
}
check_os_pathlib_single_arg_calls(
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall) {
check_os_path_get_calls(
checker,
call,
"stat().st_size",
"filename",
"getsize",
"st_size",
is_fix_os_path_getsize_enabled(checker.settings()),
OsPathGetsize,
);

View File

@@ -1,72 +0,0 @@
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,
);
}

View File

@@ -1,75 +0,0 @@
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,
);
}

View File

@@ -1,75 +0,0 @@
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,
);
}

View File

@@ -1,75 +0,0 @@
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,
);
}

View File

@@ -1,91 +0,0 @@
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,
);
}

View File

@@ -1,86 +0,0 @@
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,
);
}

View File

@@ -1,86 +0,0 @@
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,
);
}

View File

@@ -1,86 +0,0 @@
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,
);
}

View File

@@ -54,11 +54,7 @@ impl AlwaysFixableViolation for PathConstructorCurrentDirectory {
}
/// PTH201
pub(crate) fn path_constructor_current_directory(
checker: &Checker,
call: &ExprCall,
segments: &[&str],
) {
pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprCall) {
let applicability = |range| {
if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
@@ -67,9 +63,15 @@ pub(crate) fn path_constructor_current_directory(
}
};
let arguments = &call.arguments;
let (func, arguments) = (&call.func, &call.arguments);
if !matches!(segments, ["pathlib", "Path" | "PurePath"]) {
if !checker
.semantic()
.resolve_qualified_name(func)
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["pathlib", "Path" | "PurePath"])
})
{
return;
}

View File

@@ -4,12 +4,14 @@ 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, OsPathJoin,
OsPathSamefile, OsPathSplitext, OsRename, OsReplace, OsStat, OsSymlink, PyPath,
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,
};
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 {
@@ -18,6 +20,8 @@ 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.
@@ -83,10 +87,60 @@ 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.
@@ -105,6 +159,8 @@ 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 {
@@ -128,6 +184,10 @@ 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
@@ -148,7 +208,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
// `closefd` and `opener` are not supported by pathlib, so check if they are
// 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):
@@ -222,6 +282,20 @@ 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
@@ -264,7 +338,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(ExprCall { func, .. }) => get_name_expr(func),
Expr::Call(ast::ExprCall { func, .. }) => get_name_expr(func),
_ => None,
}
}
@@ -275,3 +349,9 @@ 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())
}

View File

@@ -10,7 +10,6 @@ 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()`
|
@@ -70,7 +69,6 @@ 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()`
|
@@ -81,7 +79,6 @@ 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()`
|
@@ -92,7 +89,6 @@ 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()`
|
@@ -113,7 +109,6 @@ 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()`
|
@@ -124,7 +119,6 @@ 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()`
|
@@ -135,7 +129,6 @@ 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()`
|
@@ -146,7 +139,6 @@ 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()`
|
@@ -157,7 +149,6 @@ 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()`
|
@@ -168,7 +159,6 @@ 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()`
|
@@ -189,7 +179,6 @@ 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
|
@@ -230,7 +219,6 @@ 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`
|
@@ -241,7 +229,6 @@ 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()`
|

View File

@@ -10,7 +10,6 @@ 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()`
|
@@ -70,7 +69,6 @@ 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()`
|
@@ -81,7 +79,6 @@ 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()`
|
@@ -92,7 +89,6 @@ 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()`
|
@@ -113,7 +109,6 @@ 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()`
|
@@ -124,7 +119,6 @@ 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()`
|
@@ -135,7 +129,6 @@ 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()`
|
@@ -146,7 +139,6 @@ 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()`
|
@@ -157,7 +149,6 @@ 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()`
|
@@ -168,7 +159,6 @@ 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()`
|
@@ -189,7 +179,6 @@ 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
|
@@ -230,7 +219,6 @@ 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`
|
@@ -241,7 +229,6 @@ 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()`
|

View File

@@ -10,7 +10,6 @@ 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()`
|
@@ -70,7 +69,6 @@ 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()`
|
@@ -81,7 +79,6 @@ 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()`
|
@@ -92,7 +89,6 @@ 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()`
|
@@ -113,7 +109,6 @@ 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()`
|
@@ -124,7 +119,6 @@ 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()`
|
@@ -135,7 +129,6 @@ 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()`
|
@@ -146,7 +139,6 @@ 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()`
|
@@ -157,7 +149,6 @@ 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()`
|
@@ -168,7 +159,6 @@ 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()`
|
@@ -189,7 +179,6 @@ 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
|
@@ -230,7 +219,6 @@ 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`
|
@@ -241,7 +229,6 @@ 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()`
|

View File

@@ -10,7 +10,6 @@ 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()`
|
@@ -70,7 +69,6 @@ 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()`
|
@@ -81,7 +79,6 @@ 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()`
|
@@ -92,7 +89,6 @@ 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()`
|
@@ -113,7 +109,6 @@ 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()`
|
@@ -124,7 +119,6 @@ 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()`
|
@@ -135,7 +129,6 @@ 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()`
|
@@ -146,7 +139,6 @@ 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()`
|
@@ -157,7 +149,6 @@ 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()`
|
@@ -168,7 +159,6 @@ 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()`
|
@@ -189,7 +179,6 @@ 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
|
@@ -230,7 +219,6 @@ 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`
|
@@ -241,7 +229,6 @@ 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()`
|

View File

@@ -1,580 +0,0 @@
---
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
|

View File

@@ -1,478 +0,0 @@
---
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
|

View File

@@ -1,521 +0,0 @@
---
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
|

View File

@@ -1,491 +0,0 @@
---
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
|

View File

@@ -2,6 +2,51 @@ 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`.
///
@@ -230,6 +275,141 @@ 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`.
///
@@ -276,6 +456,276 @@ 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`.
///
@@ -331,6 +781,53 @@ 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`.
///
@@ -393,6 +890,96 @@ 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`.
///

View File

@@ -60,14 +60,12 @@ impl Violation for IndentationWithInvalidMultiple {
/// ```python
/// if True:
/// # a = 1
/// ...
/// ```
///
/// Use instead:
/// ```python
/// if True:
/// # a = 1
/// ...
/// ```
///
/// ## Formatter compatibility

View File

@@ -43,12 +43,12 @@ impl AlwaysFixableViolation for MultipleSpacesAfterKeyword {
///
/// ## Example
/// ```python
/// x and y
/// True and False
/// ```
///
/// Use instead:
/// ```python
/// x and y
/// True and False
/// ```
#[derive(ViolationMetadata)]
pub(crate) struct MultipleSpacesBeforeKeyword;

View File

@@ -238,9 +238,6 @@ impl Violation for DocstringExtraneousYields {
///
/// ## Example
/// ```python
/// class FasterThanLightError(ArithmeticError): ...
///
///
/// def calculate_speed(distance: float, time: float) -> float:
/// """Calculate speed as distance divided by time.
///
@@ -259,9 +256,6 @@ 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.
///

View File

@@ -774,7 +774,7 @@ mod tests {
messages.sort_by_key(|diagnostic| diagnostic.expect_range().start());
let actual = messages
.iter()
.filter(|msg| !msg.is_invalid_syntax())
.filter(|msg| !msg.is_syntax_error())
.map(Diagnostic::name)
.collect::<Vec<_>>();
let expected: Vec<_> = expected.iter().map(|rule| rule.name().as_str()).collect();

View File

@@ -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()` or `str.rsplit()` without
/// Checks for access to the first or last element of `str.split()` without
/// `maxsplit=1`
///
/// ## Why is this bad?
/// Calling `str.split()` or `str.rsplit()` without passing `maxsplit=1` splits on every delimiter in the
/// Calling `str.split()` without `maxsplit` set 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,44 +29,14 @@ 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 {
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,
}
pub(crate) struct MissingMaxsplitArg;
impl Violation for MissingMaxsplitArg {
#[derive_message_formats]
fn message(&self) -> 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`",
)
}
"Accessing only the first or last element of `str.split()` without setting `maxsplit=1`"
.to_string()
}
}
@@ -112,11 +82,9 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
_ => return,
};
let slice_boundary = match index {
Some(0) => SliceBoundary::First,
Some(-1) => SliceBoundary::Last,
_ => return,
};
if !matches!(index, Some(0 | -1)) {
return;
}
let Expr::Attribute(ExprAttribute { attr, value, .. }) = func.as_ref() else {
return;
@@ -161,11 +129,5 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
}
}
checker.report_diagnostic(
MissingMaxsplitArg {
index: slice_boundary,
actual_split_type: attr.to_string(),
},
expr.range(),
);
checker.report_diagnostic(MissingMaxsplitArg, expr.range());
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:14:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
12 | # Errors
13 | ## Test split called directly on string literal
@@ -11,7 +11,7 @@ missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:15:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.split()`, call `str.rsplit
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:16:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:17:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.rsplit()`
19 | ## Test split called on string variable
|
missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:20:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.split()`
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:21:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.split()`, call `str.rsplit
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:22:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:23:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.rsplit()`
25 | ## Test split called on class attribute
|
missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:26:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.split()`
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:27:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.split()`, call `str.rsplit
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:28:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:29:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.rsplit()`
31 | ## Test split called on sliced string
|
missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:32:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.split()`
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:33:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.split()`
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:34:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.split()`
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:35:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.split()`, call `str.rsplit
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:36:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:37:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:38:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.rsplit()`
40 | ## Test sep given as named argument
|
missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:41:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.split()`
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:42:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.split()`, call `str.rsplit
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:43:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:44:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.rsplit()`
46 | ## Special cases
|
missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:47:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
46 | ## Special cases
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
@@ -231,7 +231,7 @@ missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:48:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.split()`, call `str.rsplit
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:49:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
51 | ## Test class attribute named split
|
missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:52:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.split()`
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:53:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.split()`, call `str.rsplit
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:54:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `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 Instead of `str.rsplit()`, call `str.split
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:55:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
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 Pass `maxsplit=1` into `str.rsplit()`
57 | ## Test unpacked dict literal kwargs
|
missing_maxsplit_arg.py:58:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:58:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
57 | ## Test unpacked dict literal kwargs
58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:179:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
177 | # Errors
178 | kwargs_without_maxsplit = {"seq": ","}
@@ -305,7 +305,7 @@ missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:182:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
180 | # OK
181 | kwargs_with_maxsplit = {"maxsplit": 1}
@@ -315,7 +315,7 @@ missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
missing_maxsplit_arg.py:184:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:184:1: PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
|
182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}

View File

@@ -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::{Edit, Fix, FixAvailability, Violation};
use crate::{AlwaysFixableViolation, Edit, Fix};
/// ## What it does
/// Checks for `super` calls that pass redundant arguments.
@@ -57,16 +57,14 @@ use crate::{Edit, Fix, FixAvailability, Violation};
#[derive(ViolationMetadata)]
pub(crate) struct SuperCallWithParameters;
impl Violation for SuperCallWithParameters {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
impl AlwaysFixableViolation for SuperCallWithParameters {
#[derive_message_formats]
fn message(&self) -> String {
"Use `super()` instead of `super(__class__, self)`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Remove `super()` parameters".to_string())
fn fix_title(&self) -> String {
"Remove `super()` parameters".to_string()
}
}
@@ -167,26 +165,22 @@ 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());
// 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,
));
}
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.

View File

@@ -249,53 +249,3 @@ 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

View File

@@ -249,53 +249,3 @@ 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

View File

@@ -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_by(Diagnostic::ruff_start_ordering)
.sorted()
.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_invalid_syntax());
diagnostics.retain(|msg| !msg.is_syntax_error());
if let Some(notebook) = source.as_ipy_notebook() {
print_jupyter_messages(&diagnostics, path, notebook)

View File

@@ -1,10 +1,6 @@
use std::fmt::{Debug, Display};
use ruff_db::diagnostic::Diagnostic;
use ruff_source_file::SourceFile;
use ruff_text_size::TextRange;
use crate::{codes::Rule, message::create_lint_diagnostic};
use crate::codes::Rule;
#[derive(Debug, Copy, Clone)]
pub enum FixAvailability {
@@ -32,7 +28,7 @@ pub trait ViolationMetadata {
fn explain() -> Option<&'static str>;
}
pub trait Violation: ViolationMetadata + Sized {
pub trait Violation: ViolationMetadata {
/// `None` in the case a fix is never available or otherwise Some
/// [`FixAvailability`] describing the available fix.
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
@@ -52,20 +48,6 @@ pub trait Violation: ViolationMetadata + Sized {
/// 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

View File

@@ -1,95 +0,0 @@
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")
}

View File

@@ -1,4 +1,4 @@
//! This crate implements internal macros for the `ruff` and `ty` libraries.
//! This crate implements internal macros for the `ruff` library.
use crate::cache_key::derive_cache_key;
use crate::newtype_index::generate_newtype_index;
@@ -11,7 +11,6 @@ mod combine;
mod combine_options;
mod config;
mod derive_message_formats;
mod env_vars;
mod kebab_case;
mod map_codes;
mod newtype_index;
@@ -145,15 +144,3 @@ 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()
}

View File

@@ -163,7 +163,7 @@ pub(crate) fn check(
.into_iter()
.zip(noqa_edits)
.filter_map(|(message, noqa_edit)| {
if message.is_invalid_syntax() && !show_syntax_errors {
if message.is_syntax_error() && !show_syntax_errors {
None
} else {
Some(to_lsp_diagnostic(

View File

@@ -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 }

View File

@@ -1,55 +0,0 @@
# 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.

View File

@@ -4,7 +4,6 @@ mod python_version;
mod version;
pub use args::Cli;
use ty_static::EnvVars;
use std::io::{self, BufWriter, Write, stdout};
use std::process::{ExitCode, Termination};
@@ -59,6 +58,7 @@ 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())?;
tracing::warn!(
@@ -144,13 +144,15 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
};
let mut stdout = stdout().lock();
match std::env::var(EnvVars::TY_MEMORY_REPORT).as_deref() {
match std::env::var("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 {
@@ -350,6 +352,8 @@ 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) => {

View File

@@ -12,7 +12,6 @@ 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)]
@@ -85,7 +84,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(EnvVars::TY_LOG) {
let filter = if let Ok(log_env_variable) = std::env::var("TY_LOG") {
EnvFilter::builder()
.parse(log_env_variable)
.context("Failed to parse directives specified in TY_LOG environment variable.")?
@@ -166,7 +165,7 @@ fn setup_profile<S>() -> (
where
S: Subscriber + for<'span> LookupSpan<'span>,
{
if let Ok("1" | "true") = std::env::var(EnvVars::TY_LOG_PROFILE).as_deref() {
if let Ok("1" | "true") = std::env::var("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))

View File

@@ -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_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
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__
");
}
@@ -1268,33 +1268,33 @@ quux.b<CURSOR>
",
);
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
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__
");
}
@@ -1321,89 +1321,6 @@ 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() {
@@ -2449,22 +2366,7 @@ 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();
@@ -2472,7 +2374,7 @@ importlib.<CURSOR>
let included = completions
.iter()
.filter(|label| predicate(label))
.map(snapshot)
.map(|completion| completion.name.as_str().to_string())
.collect::<Vec<String>>();
if included.is_empty() {
// It'd be nice to include the actual number of

View File

@@ -285,7 +285,16 @@ impl Project {
return Vec::new();
}
self.check_file_impl(db, file)
let mut file_diagnostics: Vec<_> = self
.settings_diagnostics(db)
.iter()
.map(OptionDiagnostic::to_diagnostic)
.collect();
let check_diagnostics = self.check_file_impl(db, file);
file_diagnostics.extend(check_diagnostics);
file_diagnostics
}
/// Opens a file in the project.
@@ -491,11 +500,11 @@ impl Project {
parsed_ref
.errors()
.iter()
.map(|error| Diagnostic::invalid_syntax(file, &error.error, error)),
.map(|error| Diagnostic::syntax_error(file, &error.error, error)),
);
diagnostics.extend(parsed_ref.unsupported_syntax_errors().iter().map(|error| {
let mut error = Diagnostic::invalid_syntax(file, error, error);
let mut error = Diagnostic::syntax_error(file, error, error);
add_inferred_python_version_hint_to_diagnostic(db, &mut error, "parsing syntax");
error
}));

View File

@@ -22,13 +22,13 @@ ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ruff_python_literal = { workspace = true }
ruff_python_trivia = { workspace = true }
ty_static = { workspace = true }
anyhow = { workspace = true }
bitflags = { workspace = true }
camino = { workspace = true }
colored = { workspace = true }
compact_str = { workspace = true }
countme = { workspace = true }
drop_bomb = { workspace = true }
get-size2 = { workspace = true }
indexmap = { workspace = true }
@@ -52,7 +52,6 @@ strum_macros = { workspace = true }
ruff_db = { workspace = true, features = ["testing", "os"] }
ruff_python_parser = { workspace = true }
ty_python_semantic = { workspace = true, features = ["testing"] }
ty_static = { workspace = true }
ty_test = { workspace = true }
ty_vendored = { workspace = true }

View File

@@ -1791,80 +1791,6 @@ date.year = 2025
date.tz = "UTC"
```
### Return type of `__setattr__`
If the return type of the `__setattr__` method is `Never`, we do not allow any attribute assignments
on instances of that class:
```py
from typing_extensions import Never
class Frozen:
existing: int = 1
def __setattr__(self, name, value) -> Never:
raise AttributeError("Attributes can not be modified")
instance = Frozen()
instance.non_existing = 2 # error: [invalid-assignment] "Cannot assign to attribute `non_existing` on type `Frozen` whose `__setattr__` method returns `Never`/`NoReturn`"
instance.existing = 2 # error: [invalid-assignment] "Cannot assign to attribute `existing` on type `Frozen` whose `__setattr__` method returns `Never`/`NoReturn`"
```
### `__setattr__` on `object`
`object` has a custom `__setattr__` implementation, but we still emit an error if a non-existing
attribute is assigned on an `object` instance.
```py
obj = object()
obj.non_existing = 1 # error: [unresolved-attribute]
```
### Setting attributes on `Never` / `Any`
Setting attributes on `Never` itself should be allowed (even though it has a `__setattr__` attribute
of type `Never`):
```py
from typing_extensions import Never, Any
def _(n: Never):
reveal_type(n.__setattr__) # revealed: Never
# No error:
n.non_existing = 1
```
And similarly for `Any`:
```py
def _(a: Any):
reveal_type(a.__setattr__) # revealed: Any
# No error:
a.non_existing = 1
```
### Possibly unbound `__setattr__` method
If a `__setattr__` method is only partially bound, the behavior is still the same:
```py
from typing_extensions import Never
def flag() -> bool:
return True
class Frozen:
if flag():
def __setattr__(self, name, value) -> Never:
raise AttributeError("Attributes can not be modified")
instance = Frozen()
instance.non_existing = 2 # error: [invalid-assignment]
instance.existing = 2 # error: [invalid-assignment]
```
### `argparse.Namespace`
A standard library example of a class with a custom `__setattr__` method is `argparse.Namespace`:

View File

@@ -558,50 +558,6 @@ class C(Base):
reveal_type(C.__init__) # revealed: (self: C, x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
```
## Conditionally defined fields
### Statically known conditions
Fields that are defined in always-reachable branches are always present in the synthesized
`__init__` method. Fields that are defined in never-reachable branches are not present:
```py
from dataclasses import dataclass
@dataclass
class C:
normal: int
if 1 + 2 == 3:
always_present: str
if 1 + 2 == 4:
never_present: bool
reveal_type(C.__init__) # revealed: (self: C, normal: int, always_present: str) -> None
```
### Dynamic conditions
If a field is conditionally defined, we currently assume that it is always present. A more complex
alternative here would be to synthesized a union of all possible `__init__` signatures:
```py
from dataclasses import dataclass
def flag() -> bool:
return True
@dataclass
class C:
normal: int
if flag():
conditionally_present: str
reveal_type(C.__init__) # revealed: (self: C, normal: int, conditionally_present: str) -> None
```
## Generic dataclasses
```toml
@@ -833,23 +789,6 @@ class Fails: # error: [duplicate-kw-only]
reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
```
This also works if `KW_ONLY` is used in a conditional branch:
```py
def flag() -> bool:
return True
@dataclass
class D: # error: [duplicate-kw-only]
x: int
_1: KW_ONLY
if flag():
y: str
_2: KW_ONLY
z: float
```
## Other special cases
### `dataclasses.dataclass`

View File

@@ -1,35 +0,0 @@
# Dataclass fields
## Basic
```py
from dataclasses import dataclass, field
@dataclass
class Member:
name: str
role: str = field(default="user")
tag: str | None = field(default=None, init=False)
# TODO: this should not include the `tag` parameter, since it has `init=False` set
# revealed: (self: Member, name: str, role: str = Unknown, tag: str | None = Unknown) -> None
reveal_type(Member.__init__)
alice = Member(name="Alice", role="admin")
reveal_type(alice.role) # revealed: str
alice.role = "moderator"
# TODO: this should be an error, `tag` has `init=False`
bob = Member(name="Bob", tag="VIP")
```
## The `field` function
```py
from dataclasses import field
# TODO: this should be `Literal[1]`. This is currently blocked on enum support, because
# the `dataclasses.field` overloads make use of a `_MISSING_TYPE` enum, for which we
# infer a @Todo type, and therefore pick the wrong overload.
reveal_type(field(default=1)) # revealed: Unknown
```

View File

@@ -37,18 +37,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.
23 | e: bytes
24 |
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
26 | def flag() -> bool:
27 | return True
28 |
29 | @dataclass
30 | class D: # error: [duplicate-kw-only]
31 | x: int
32 | _1: KW_ONLY
33 |
34 | if flag():
35 | y: str
36 | _2: KW_ONLY
37 | z: float
```
# Diagnostics
@@ -121,23 +109,6 @@ info[revealed-type]: Revealed type
24 |
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
| ^^^^^^^^^^^^^^ `(self: Fails, a: int, *, c: str, e: bytes) -> None`
26 | def flag() -> bool:
27 | return True
|
```
```
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
--> src/mdtest_snippet.py:30:7
|
29 | @dataclass
30 | class D: # error: [duplicate-kw-only]
| ^
31 | x: int
32 | _1: KW_ONLY
|
info: `KW_ONLY` fields: `_1`, `_2`
info: rule `duplicate-kw-only` is enabled by default
```

View File

@@ -1,59 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: final.md - `typing.Final` - Full diagnostics
mdtest path: crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import Final
2 |
3 | MY_CONSTANT: Final[int] = 1
4 |
5 | # more code
6 |
7 | MY_CONSTANT = 2 # error: [invalid-assignment]
8 | from _stat import ST_INO
9 |
10 | ST_INO = 1 # error: [invalid-assignment]
```
# Diagnostics
```
error[invalid-assignment]: Reassignment of `Final` symbol `MY_CONSTANT` is not allowed
--> src/mdtest_snippet.py:3:14
|
1 | from typing import Final
2 |
3 | MY_CONSTANT: Final[int] = 1
| ---------- Symbol declared as `Final` here
4 |
5 | # more code
6 |
7 | MY_CONSTANT = 2 # error: [invalid-assignment]
| ^^^^^^^^^^^^^^^ Symbol later reassigned here
8 | from _stat import ST_INO
|
info: rule `invalid-assignment` is enabled by default
```
```
error[invalid-assignment]: Reassignment of `Final` symbol `ST_INO` is not allowed
--> src/mdtest_snippet.py:10:1
|
8 | from _stat import ST_INO
9 |
10 | ST_INO = 1 # error: [invalid-assignment]
| ^^^^^^^^^^ Reassignment of `Final` symbol
|
info: rule `invalid-assignment` is enabled by default
```

View File

@@ -82,7 +82,7 @@ info: Type variable defined here
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", bound=int)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^
5 |
6 | def f(x: T) -> T:
|

View File

@@ -97,7 +97,7 @@ info: Type variable defined here
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", int, None)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^
5 |
6 | def f(x: T) -> T:
|

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