Compare commits
18 Commits
jack/not_l
...
jack/nonlo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f22497bdd | ||
|
|
05cf7c3458 | ||
|
|
664a9a28dc | ||
|
|
965f415212 | ||
|
|
83b5bbf004 | ||
|
|
87f6f08ef5 | ||
|
|
59114d0301 | ||
|
|
492f5bf2aa | ||
|
|
934aaa23f3 | ||
|
|
59aa869724 | ||
|
|
edaffa6c4f | ||
|
|
5fb2fb916b | ||
|
|
801f69a7b4 | ||
|
|
3926dd8424 | ||
|
|
563268ce53 | ||
|
|
221edcba5c | ||
|
|
beb98dae7c | ||
|
|
05b1b788a0 |
1
.github/workflows/mypy_primer.yaml
vendored
1
.github/workflows/mypy_primer.yaml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
- ".github/workflows/mypy_primer.yaml"
|
||||
- ".github/workflows/mypy_primer_comment.yaml"
|
||||
- "Cargo.lock"
|
||||
- "!**.md"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
|
||||
76
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
76
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -17,6 +17,7 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
@@ -63,32 +64,75 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@9c34dc514ee9aef6735db1dfebb80f63acbc3440"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@f0eec0e549684d8e1d7b8bc3e351202124b63bda"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--projects ruff/projects_old.txt \
|
||||
--commit old_commit \
|
||||
--output diagnostics_old.json
|
||||
diff \
|
||||
--projects-old ruff/projects_old.txt \
|
||||
--projects-new ruff/projects_new.txt \
|
||||
--old old_commit \
|
||||
--new new_commit \
|
||||
--output-old diagnostics-old.json \
|
||||
--output-new diagnostics-new.json
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
analyze \
|
||||
--projects ruff/projects_new.txt \
|
||||
--commit new_commit \
|
||||
--output diagnostics_new.json
|
||||
mkdir dist
|
||||
|
||||
ecosystem-analyzer \
|
||||
generate-diff \
|
||||
diagnostics_old.json \
|
||||
diagnostics_new.json \
|
||||
diagnostics-old.json \
|
||||
diagnostics-new.json \
|
||||
--old-name "main (merge base)" \
|
||||
--new-name "$REF_NAME" \
|
||||
--output-html diff.html
|
||||
--output-html dist/diff.html
|
||||
|
||||
- name: Upload HTML diff report
|
||||
ecosystem-analyzer \
|
||||
generate-diff-statistics \
|
||||
diagnostics-old.json \
|
||||
diagnostics-new.json \
|
||||
--old-name "main (merge base)" \
|
||||
--new-name "$REF_NAME" \
|
||||
--output diff-statistics.md
|
||||
|
||||
echo '## `ecosystem-analyzer` results' > comment.md
|
||||
echo >> comment.md
|
||||
cat diff-statistics.md >> comment.md
|
||||
|
||||
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
- name: "Append deployment URL"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
env:
|
||||
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||
run: |
|
||||
echo >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)**" >> comment.md
|
||||
|
||||
- name: Upload comment
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: comment.md
|
||||
path: comment.md
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
- name: Upload diagnostics diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: diff.html
|
||||
path: diff.html
|
||||
path: dist/diff.html
|
||||
|
||||
1
.github/zizmor.yml
vendored
1
.github/zizmor.yml
vendored
@@ -10,6 +10,7 @@ rules:
|
||||
ignore:
|
||||
- build-docker.yml
|
||||
- publish-playground.yml
|
||||
- ty-ecosystem-analyzer.yaml
|
||||
excessive-permissions:
|
||||
# it's hard to test what the impact of removing these ignores would be
|
||||
# without actually running the release workflow...
|
||||
|
||||
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -680,11 +680,6 @@ name = "countme"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
dependencies = [
|
||||
"dashmap 5.5.3",
|
||||
"once_cell",
|
||||
"rustc-hash 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
@@ -852,19 +847,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
@@ -2262,7 +2244,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
"regex",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
@@ -2772,7 +2754,7 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_workspace",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
@@ -2818,7 +2800,7 @@ dependencies = [
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tikv-jemallocator",
|
||||
@@ -2847,7 +2829,7 @@ dependencies = [
|
||||
"arc-swap",
|
||||
"camino",
|
||||
"countme",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"dunce",
|
||||
"etcetera",
|
||||
"filetime",
|
||||
@@ -2866,7 +2848,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2940,7 +2922,7 @@ dependencies = [
|
||||
"ruff_cache",
|
||||
"ruff_macros",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
@@ -3022,7 +3004,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3094,7 +3076,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -3144,7 +3126,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -3193,7 +3175,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"static_assertions",
|
||||
@@ -3217,7 +3199,7 @@ dependencies = [
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"smallvec",
|
||||
@@ -3278,7 +3260,7 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"ruff_workspace",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
@@ -3367,7 +3349,7 @@ dependencies = [
|
||||
"ruff_python_semantic",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"shellexpand",
|
||||
@@ -3386,12 +3368,6 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -3446,7 +3422,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa-macro-rules",
|
||||
"salsa-macros",
|
||||
"smallvec",
|
||||
@@ -4144,7 +4120,6 @@ dependencies = [
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
"colored 3.0.0",
|
||||
"countme",
|
||||
"crossbeam",
|
||||
"ctrlc",
|
||||
"dunce",
|
||||
@@ -4181,7 +4156,7 @@ dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
@@ -4213,7 +4188,7 @@ dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4234,7 +4209,6 @@ dependencies = [
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"compact_str",
|
||||
"countme",
|
||||
"dir-test",
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
@@ -4258,7 +4232,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4290,7 +4264,7 @@ dependencies = [
|
||||
"ruff_notebook",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4328,7 +4302,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustc-hash",
|
||||
"rustc-stable-hash",
|
||||
"salsa",
|
||||
"serde",
|
||||
|
||||
@@ -430,6 +430,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Babel](https://github.com/python-babel/babel)
|
||||
- Benchling ([Refac](https://github.com/benchling/refac))
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- Capital One ([datacompy](https://github.com/capitalone/datacompy))
|
||||
- CrowdCent ([NumerBlox](https://github.com/crowdcent/numerblox)) <!-- typos: ignore -->
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- CERN ([Indico](https://getindico.io/))
|
||||
|
||||
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/whitespace.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/whitespace.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/19175
|
||||
# there is a (potentially invisible) unicode formfeed character (000C) between `TYPE_CHECKING` and the backslash
|
||||
from typing import TYPE_CHECKING\
|
||||
|
||||
if TYPE_CHECKING: import builtins
|
||||
builtins.print("!")
|
||||
@@ -125,3 +125,19 @@ class ClassForCommentEnthusiasts(BaseClass):
|
||||
self
|
||||
# also a comment
|
||||
).f()
|
||||
|
||||
|
||||
# Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed
|
||||
class Ord(int):
|
||||
def __len__(self):
|
||||
return super(Ord, self, uhoh=True, **{"error": True}).bit_length()
|
||||
|
||||
class ExampleWithKeywords:
|
||||
def method1(self):
|
||||
super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed
|
||||
|
||||
def method2(self):
|
||||
super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
|
||||
def method3(self):
|
||||
super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
|
||||
@@ -1039,27 +1039,14 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
|
||||
}
|
||||
if checker.any_rule_enabled(&[
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::OsReplace,
|
||||
Rule::OsRmdir,
|
||||
Rule::OsRemove,
|
||||
Rule::OsUnlink,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsPathExists,
|
||||
Rule::OsPathExpanduser,
|
||||
Rule::OsPathIsdir,
|
||||
Rule::OsPathIsfile,
|
||||
Rule::OsPathIslink,
|
||||
Rule::OsReadlink,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathIsabs,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathBasename,
|
||||
Rule::OsPathDirname,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
@@ -1070,21 +1057,66 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
]) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetsize) {
|
||||
flake8_use_pathlib::rules::os_path_getsize(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetatime) {
|
||||
flake8_use_pathlib::rules::os_path_getatime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetctime) {
|
||||
flake8_use_pathlib::rules::os_path_getctime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetmtime) {
|
||||
flake8_use_pathlib::rules::os_path_getmtime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, call);
|
||||
if let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) {
|
||||
let segments = qualified_name.segments();
|
||||
if checker.is_rule_enabled(Rule::OsPathGetsize) {
|
||||
flake8_use_pathlib::rules::os_path_getsize(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetatime) {
|
||||
flake8_use_pathlib::rules::os_path_getatime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetctime) {
|
||||
flake8_use_pathlib::rules::os_path_getctime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetmtime) {
|
||||
flake8_use_pathlib::rules::os_path_getmtime(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathAbspath) {
|
||||
flake8_use_pathlib::rules::os_path_abspath(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsRmdir) {
|
||||
flake8_use_pathlib::rules::os_rmdir(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsRemove) {
|
||||
flake8_use_pathlib::rules::os_remove(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsUnlink) {
|
||||
flake8_use_pathlib::rules::os_unlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathExists) {
|
||||
flake8_use_pathlib::rules::os_path_exists(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathExpanduser) {
|
||||
flake8_use_pathlib::rules::os_path_expanduser(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathBasename) {
|
||||
flake8_use_pathlib::rules::os_path_basename(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathDirname) {
|
||||
flake8_use_pathlib::rules::os_path_dirname(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsabs) {
|
||||
flake8_use_pathlib::rules::os_path_isabs(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsdir) {
|
||||
flake8_use_pathlib::rules::os_path_isdir(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIsfile) {
|
||||
flake8_use_pathlib::rules::os_path_isfile(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathIslink) {
|
||||
flake8_use_pathlib::rules::os_path_islink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsReadlink) {
|
||||
flake8_use_pathlib::rules::os_readlink(checker, call, segments);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(
|
||||
checker, call, segments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::OsSepSplit) {
|
||||
flake8_use_pathlib::rules::os_sep_split(checker, call);
|
||||
}
|
||||
|
||||
@@ -670,7 +670,12 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
| SemanticSyntaxErrorKind::InvalidStarExpression
|
||||
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateParameter(_)
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel
|
||||
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
|
||||
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
|
||||
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_)
|
||||
| SemanticSyntaxErrorKind::InvalidNonlocal(_) => {
|
||||
self.semantic_errors.borrow_mut().push(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,27 +919,27 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Tryceratops, "401") => (RuleGroup::Stable, rules::tryceratops::rules::VerboseLogMessage),
|
||||
|
||||
// flake8-use-pathlib
|
||||
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathAbspath),
|
||||
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
|
||||
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsChmod),
|
||||
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
|
||||
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
|
||||
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRename),
|
||||
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsReplace),
|
||||
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRmdir),
|
||||
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsRemove),
|
||||
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsUnlink),
|
||||
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),
|
||||
(Flake8UsePathlib, "107") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRemove),
|
||||
(Flake8UsePathlib, "108") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsUnlink),
|
||||
(Flake8UsePathlib, "109") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsGetcwd),
|
||||
(Flake8UsePathlib, "110") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathExists),
|
||||
(Flake8UsePathlib, "111") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathExpanduser),
|
||||
(Flake8UsePathlib, "112") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsdir),
|
||||
(Flake8UsePathlib, "113") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsfile),
|
||||
(Flake8UsePathlib, "114") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIslink),
|
||||
(Flake8UsePathlib, "115") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsReadlink),
|
||||
(Flake8UsePathlib, "110") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExists),
|
||||
(Flake8UsePathlib, "111") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathExpanduser),
|
||||
(Flake8UsePathlib, "112") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsdir),
|
||||
(Flake8UsePathlib, "113") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsfile),
|
||||
(Flake8UsePathlib, "114") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIslink),
|
||||
(Flake8UsePathlib, "115") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReadlink),
|
||||
(Flake8UsePathlib, "116") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsStat),
|
||||
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathIsabs),
|
||||
(Flake8UsePathlib, "117") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathIsabs),
|
||||
(Flake8UsePathlib, "118") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathJoin),
|
||||
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathBasename),
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathDirname),
|
||||
(Flake8UsePathlib, "119") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathBasename),
|
||||
(Flake8UsePathlib, "120") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathDirname),
|
||||
(Flake8UsePathlib, "121") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSamefile),
|
||||
(Flake8UsePathlib, "122") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsPathSplitext),
|
||||
(Flake8UsePathlib, "123") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::BuiltinOpen),
|
||||
|
||||
@@ -5,6 +5,7 @@ use ruff_python_ast::Stmt;
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::{TokenKind, Tokens};
|
||||
use ruff_python_trivia::is_python_whitespace;
|
||||
use ruff_python_trivia::{PythonWhitespace, textwrap::indent};
|
||||
use ruff_source_file::{LineRanges, UniversalNewlineIterator};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
@@ -306,7 +307,7 @@ fn match_semicolon(s: &str) -> Option<TextSize> {
|
||||
fn match_continuation(s: &str) -> Option<TextSize> {
|
||||
for (offset, c) in s.char_indices() {
|
||||
match c {
|
||||
' ' | '\t' => continue,
|
||||
_ if is_python_whitespace(c) => continue,
|
||||
'\\' => return Some(TextSize::try_from(offset).unwrap()),
|
||||
_ => break,
|
||||
}
|
||||
|
||||
@@ -69,6 +69,71 @@ pub(crate) const fn is_fix_os_path_getctime_enabled(settings: &LinterSettings) -
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_abspath_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_rmdir_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_unlink_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_remove_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_exists_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_expanduser_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_isdir_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_isfile_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_islink_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_isabs_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_readlink_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_basename_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19213
|
||||
pub(crate) const fn is_fix_os_path_dirname_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/11436
|
||||
// https://github.com/astral-sh/ruff/pull/11168
|
||||
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {
|
||||
|
||||
@@ -36,6 +36,7 @@ mod tests {
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TC004_8.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TC004_9.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("quote.py"))]
|
||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("whitespace.py"))]
|
||||
#[test_case(Rule::RuntimeStringUnion, Path::new("TC010_1.py"))]
|
||||
#[test_case(Rule::RuntimeStringUnion, Path::new("TC010_2.py"))]
|
||||
#[test_case(Rule::TypingOnlyFirstPartyImport, Path::new("TC001.py"))]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
whitespace.py:5:26: TC004 [*] Move import `builtins` out of type-checking block. Import is used for more than type hinting.
|
||||
|
|
||||
3 | from typing import TYPE_CHECKING\
|
||||
4 |
|
||||
5 | if TYPE_CHECKING: import builtins
|
||||
| ^^^^^^^^ TC004
|
||||
6 | builtins.print("!")
|
||||
|
|
||||
= help: Move out of type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | # Regression test for: https://github.com/astral-sh/ruff/issues/19175
|
||||
2 2 | # there is a (potentially invisible) unicode formfeed character (000C) between `TYPE_CHECKING` and the backslash
|
||||
3 |-from typing import TYPE_CHECKING\
|
||||
3 |+from typing import TYPE_CHECKING; import builtins\
|
||||
4 4 |
|
||||
5 |-if TYPE_CHECKING: import builtins
|
||||
5 |+if TYPE_CHECKING: pass
|
||||
6 6 | builtins.print("!")
|
||||
@@ -1,10 +1,17 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::{Applicability, Edit, Fix, Violation};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
pub(crate) fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
pub(crate) fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool {
|
||||
arguments
|
||||
.find_keyword(name)
|
||||
.is_some_and(|keyword| !keyword.value.is_none_literal_expr())
|
||||
}
|
||||
|
||||
pub(crate) fn is_pathlib_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
expr.as_call_expr().is_some_and(|expr_call| {
|
||||
checker
|
||||
.semantic()
|
||||
@@ -13,27 +20,22 @@ pub(crate) fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn check_os_path_get_calls(
|
||||
/// We check functions that take only 1 argument, this does not apply to functions
|
||||
/// with `dir_fd` argument, because `dir_fd` is not supported by pathlib,
|
||||
/// so check if it's set to non-default values
|
||||
pub(crate) fn check_os_pathlib_single_arg_calls(
|
||||
checker: &Checker,
|
||||
call: &ExprCall,
|
||||
fn_name: &str,
|
||||
attr: &str,
|
||||
fn_argument: &str,
|
||||
fix_enabled: bool,
|
||||
violation: impl Violation,
|
||||
) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(&call.func)
|
||||
.is_none_or(|qualified_name| qualified_name.segments() != ["os", "path", fn_name])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument_value("filename", 0) else {
|
||||
let Some(arg) = call.arguments.find_argument_value(fn_argument, 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -56,10 +58,10 @@ pub(crate) fn check_os_path_get_calls(
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
let replacement = if is_path_call(checker, arg) {
|
||||
format!("{arg_code}.stat().{attr}")
|
||||
let replacement = if is_pathlib_path_call(checker, arg) {
|
||||
format!("{arg_code}.{attr}")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).stat().{attr}")
|
||||
format!("{binding}({arg_code}).{attr}")
|
||||
};
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
|
||||
@@ -80,6 +80,48 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("full_name.py"))]
|
||||
#[test_case(Path::new("import_as.py"))]
|
||||
#[test_case(Path::new("import_from_as.py"))]
|
||||
#[test_case(Path::new("import_from.py"))]
|
||||
fn preview_rules(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview_{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_use_pathlib").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(vec![
|
||||
Rule::OsPathAbspath,
|
||||
Rule::OsChmod,
|
||||
Rule::OsMkdir,
|
||||
Rule::OsMakedirs,
|
||||
Rule::OsRename,
|
||||
Rule::OsReplace,
|
||||
Rule::OsRmdir,
|
||||
Rule::OsRemove,
|
||||
Rule::OsUnlink,
|
||||
Rule::OsGetcwd,
|
||||
Rule::OsPathExists,
|
||||
Rule::OsPathExpanduser,
|
||||
Rule::OsPathIsdir,
|
||||
Rule::OsPathIsfile,
|
||||
Rule::OsPathIslink,
|
||||
Rule::OsReadlink,
|
||||
Rule::OsStat,
|
||||
Rule::OsPathIsabs,
|
||||
Rule::OsPathJoin,
|
||||
Rule::OsPathBasename,
|
||||
Rule::OsPathDirname,
|
||||
Rule::OsPathSamefile,
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202.py"))]
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202_2.py"))]
|
||||
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
pub(crate) use glob_rule::*;
|
||||
pub(crate) use invalid_pathlib_with_suffix::*;
|
||||
pub(crate) use os_path_abspath::*;
|
||||
pub(crate) use os_path_basename::*;
|
||||
pub(crate) use os_path_dirname::*;
|
||||
pub(crate) use os_path_exists::*;
|
||||
pub(crate) use os_path_expanduser::*;
|
||||
pub(crate) use os_path_getatime::*;
|
||||
pub(crate) use os_path_getctime::*;
|
||||
pub(crate) use os_path_getmtime::*;
|
||||
pub(crate) use os_path_getsize::*;
|
||||
pub(crate) use os_path_isabs::*;
|
||||
pub(crate) use os_path_isdir::*;
|
||||
pub(crate) use os_path_isfile::*;
|
||||
pub(crate) use os_path_islink::*;
|
||||
pub(crate) use os_readlink::*;
|
||||
pub(crate) use os_remove::*;
|
||||
pub(crate) use os_rmdir::*;
|
||||
pub(crate) use os_sep_split::*;
|
||||
pub(crate) use os_unlink::*;
|
||||
pub(crate) use path_constructor_current_directory::*;
|
||||
pub(crate) use replaceable_by_pathlib::*;
|
||||
|
||||
mod glob_rule;
|
||||
mod invalid_pathlib_with_suffix;
|
||||
mod os_path_abspath;
|
||||
mod os_path_basename;
|
||||
mod os_path_dirname;
|
||||
mod os_path_exists;
|
||||
mod os_path_expanduser;
|
||||
mod os_path_getatime;
|
||||
mod os_path_getctime;
|
||||
mod os_path_getmtime;
|
||||
mod os_path_getsize;
|
||||
mod os_path_isabs;
|
||||
mod os_path_isdir;
|
||||
mod os_path_isfile;
|
||||
mod os_path_islink;
|
||||
mod os_readlink;
|
||||
mod os_remove;
|
||||
mod os_rmdir;
|
||||
mod os_sep_split;
|
||||
mod os_unlink;
|
||||
mod path_constructor_current_directory;
|
||||
mod replaceable_by_pathlib;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_abspath_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.abspath`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.resolve()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.abspath()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// file_path = os.path.abspath("../path/to/file")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// file_path = Path("../path/to/file").resolve()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
|
||||
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathAbspath;
|
||||
|
||||
impl Violation for OsPathAbspath {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.abspath()` should be replaced by `Path.resolve()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).resolve()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH100
|
||||
pub(crate) fn os_path_abspath(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "abspath"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"resolve()",
|
||||
"path",
|
||||
is_fix_os_path_abspath_enabled(checker.settings()),
|
||||
OsPathAbspath,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_basename_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.basename`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.name` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.basename()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.basename(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).name
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathBasename;
|
||||
|
||||
impl Violation for OsPathBasename {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.basename()` should be replaced by `Path.name`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).name`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH119
|
||||
pub(crate) fn os_path_basename(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "basename"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"name",
|
||||
"p",
|
||||
is_fix_os_path_basename_enabled(checker.settings()),
|
||||
OsPathBasename,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_dirname_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.dirname`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.parent` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.dirname()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.dirname(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).parent
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
|
||||
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathDirname;
|
||||
|
||||
impl Violation for OsPathDirname {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.dirname()` should be replaced by `Path.parent`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).parent`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH120
|
||||
pub(crate) fn os_path_dirname(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "dirname"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"parent",
|
||||
"p",
|
||||
is_fix_os_path_dirname_enabled(checker.settings()),
|
||||
OsPathDirname,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_exists_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.exists`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.exists()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.exists()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.exists("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").exists()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
|
||||
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExists;
|
||||
|
||||
impl Violation for OsPathExists {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.exists()` should be replaced by `Path.exists()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).exists()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH110
|
||||
pub(crate) fn os_path_exists(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "exists"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"exists()",
|
||||
"path",
|
||||
is_fix_os_path_exists_enabled(checker.settings()),
|
||||
OsPathExists,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_expanduser_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.expanduser`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.expanduser()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.expanduser()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.expanduser("~/films/Monty Python")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("~/films/Monty Python").expanduser()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExpanduser;
|
||||
|
||||
impl Violation for OsPathExpanduser {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.expanduser()` should be replaced by `Path.expanduser()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).expanduser()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH111
|
||||
pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "expanduser"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"expanduser()",
|
||||
"path",
|
||||
is_fix_os_path_expanduser_enabled(checker.settings()),
|
||||
OsPathExpanduser,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getatime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -61,12 +61,15 @@ impl Violation for OsPathGetatime {
|
||||
}
|
||||
|
||||
/// PTH203
|
||||
pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getatime"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getatime",
|
||||
"st_atime",
|
||||
"stat().st_atime",
|
||||
"filename",
|
||||
is_fix_os_path_getatime_enabled(checker.settings()),
|
||||
OsPathGetatime,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getctime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -62,12 +62,15 @@ impl Violation for OsPathGetctime {
|
||||
}
|
||||
|
||||
/// PTH205
|
||||
pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getctime"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getctime",
|
||||
"st_ctime",
|
||||
"stat().st_ctime",
|
||||
"filename",
|
||||
is_fix_os_path_getctime_enabled(checker.settings()),
|
||||
OsPathGetctime,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getmtime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -62,12 +62,15 @@ impl Violation for OsPathGetmtime {
|
||||
}
|
||||
|
||||
/// PTH204
|
||||
pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getmtime"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getmtime",
|
||||
"st_mtime",
|
||||
"stat().st_mtime",
|
||||
"filename",
|
||||
is_fix_os_path_getmtime_enabled(checker.settings()),
|
||||
OsPathGetmtime,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getsize_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
@@ -62,12 +62,15 @@ impl Violation for OsPathGetsize {
|
||||
}
|
||||
|
||||
/// PTH202
|
||||
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "getsize"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"getsize",
|
||||
"st_size",
|
||||
"stat().st_size",
|
||||
"filename",
|
||||
is_fix_os_path_getsize_enabled(checker.settings()),
|
||||
OsPathGetsize,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isabs_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isabs`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_absolute()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.isabs()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// if os.path.isabs(file_name):
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// if Path(file_name).is_absolute():
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
|
||||
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsabs;
|
||||
|
||||
impl Violation for OsPathIsabs {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isabs()` should be replaced by `Path.is_absolute()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_absolute()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH117
|
||||
pub(crate) fn os_path_isabs(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "isabs"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_absolute()",
|
||||
"s",
|
||||
is_fix_os_path_isabs_enabled(checker.settings()),
|
||||
OsPathIsabs,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isdir_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_dir()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isdir("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_dir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
|
||||
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsdir;
|
||||
|
||||
impl Violation for OsPathIsdir {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isdir()` should be replaced by `Path.is_dir()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_dir()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH112
|
||||
pub(crate) fn os_path_isdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "isdir"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_dir()",
|
||||
"s",
|
||||
is_fix_os_path_isdir_enabled(checker.settings()),
|
||||
OsPathIsdir,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isfile_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isfile`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_file()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isfile()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isfile("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_file()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
|
||||
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsfile;
|
||||
|
||||
impl Violation for OsPathIsfile {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isfile()` should be replaced by `Path.is_file()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_file()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH113
|
||||
pub(crate) fn os_path_isfile(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "isfile"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_file()",
|
||||
"path",
|
||||
is_fix_os_path_isfile_enabled(checker.settings()),
|
||||
OsPathIsfile,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_islink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.islink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_symlink()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.islink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.islink("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_symlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIslink;
|
||||
|
||||
impl Violation for OsPathIslink {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.islink()` should be replaced by `Path.is_symlink()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).is_symlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH114
|
||||
pub(crate) fn os_path_islink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
if segments != ["os", "path", "islink"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"is_symlink()",
|
||||
"path",
|
||||
is_fix_os_path_islink_enabled(checker.settings()),
|
||||
OsPathIslink,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_readlink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{ExprCall, PythonVersion};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.readlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.readlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.readlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.readlink(file_name)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(file_name).readlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
|
||||
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsReadlink;
|
||||
|
||||
impl Violation for OsReadlink {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.readlink()` should be replaced by `Path.readlink()`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).readlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH115
|
||||
pub(crate) fn os_readlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// Python 3.9+
|
||||
if checker.target_version() < PythonVersion::PY39 {
|
||||
return;
|
||||
}
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.readlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.readlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "readlink"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"readlink()",
|
||||
"path",
|
||||
is_fix_os_readlink_enabled(checker.settings()),
|
||||
OsReadlink,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_remove_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.remove`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.remove()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.remove("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRemove;
|
||||
|
||||
impl Violation for OsRemove {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.remove()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).unlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH107
|
||||
pub(crate) fn os_remove(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.remove)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.remove(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "remove"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"unlink()",
|
||||
"path",
|
||||
is_fix_os_remove_enabled(checker.settings()),
|
||||
OsRemove,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_rmdir_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.rmdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.rmdir()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.rmdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.rmdir("folder/")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("folder/").rmdir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
|
||||
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRmdir;
|
||||
|
||||
impl Violation for OsRmdir {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.rmdir()` should be replaced by `Path.rmdir()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).rmdir()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH106
|
||||
pub(crate) fn os_rmdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.rmdir(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "rmdir"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"rmdir()",
|
||||
"path",
|
||||
is_fix_os_rmdir_enabled(checker.settings()),
|
||||
OsRmdir,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_unlink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.unlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.unlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.unlink("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsUnlink;
|
||||
|
||||
impl Violation for OsUnlink {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.unlink()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path(...).unlink()`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH108
|
||||
pub(crate) fn os_unlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.unlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.unlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if segments != ["os", "unlink"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"unlink()",
|
||||
"path",
|
||||
is_fix_os_unlink_enabled(checker.settings()),
|
||||
OsUnlink,
|
||||
);
|
||||
}
|
||||
@@ -54,7 +54,11 @@ impl AlwaysFixableViolation for PathConstructorCurrentDirectory {
|
||||
}
|
||||
|
||||
/// PTH201
|
||||
pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprCall) {
|
||||
pub(crate) fn path_constructor_current_directory(
|
||||
checker: &Checker,
|
||||
call: &ExprCall,
|
||||
segments: &[&str],
|
||||
) {
|
||||
let applicability = |range| {
|
||||
if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
@@ -63,15 +67,9 @@ pub(crate) fn path_constructor_current_directory(checker: &Checker, call: &ExprC
|
||||
}
|
||||
};
|
||||
|
||||
let (func, arguments) = (&call.func, &call.arguments);
|
||||
let arguments = &call.arguments;
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["pathlib", "Path" | "PurePath"])
|
||||
})
|
||||
{
|
||||
if !matches!(segments, ["pathlib", "Path" | "PurePath"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@ use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_use_pathlib::helpers::is_keyword_only_argument_non_default;
|
||||
use crate::rules::flake8_use_pathlib::rules::Glob;
|
||||
use crate::rules::flake8_use_pathlib::violations::{
|
||||
BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathAbspath,
|
||||
OsPathBasename, OsPathDirname, OsPathExists, OsPathExpanduser, OsPathIsabs, OsPathIsdir,
|
||||
OsPathIsfile, OsPathIslink, OsPathJoin, OsPathSamefile, OsPathSplitext, OsReadlink, OsRemove,
|
||||
OsRename, OsReplace, OsRmdir, OsStat, OsSymlink, OsUnlink, PyPath,
|
||||
BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathJoin,
|
||||
OsPathSamefile, OsPathSplitext, OsRename, OsReplace, OsStat, OsSymlink, PyPath,
|
||||
};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) else {
|
||||
@@ -20,8 +18,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
|
||||
let range = call.func.range();
|
||||
match qualified_name.segments() {
|
||||
// PTH100
|
||||
["os", "path", "abspath"] => checker.report_diagnostic_if_enabled(OsPathAbspath, range),
|
||||
// PTH101
|
||||
["os", "chmod"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
@@ -87,60 +83,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsReplace, range)
|
||||
}
|
||||
// PTH106
|
||||
["os", "rmdir"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.rmdir(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsRmdir, range)
|
||||
}
|
||||
// PTH107
|
||||
["os", "remove"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.remove)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.remove(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsRemove, range)
|
||||
}
|
||||
// PTH108
|
||||
["os", "unlink"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.unlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.unlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsUnlink, range)
|
||||
}
|
||||
// PTH109
|
||||
["os", "getcwd"] => checker.report_diagnostic_if_enabled(OsGetcwd, range),
|
||||
["os", "getcwdb"] => checker.report_diagnostic_if_enabled(OsGetcwd, range),
|
||||
// PTH110
|
||||
["os", "path", "exists"] => checker.report_diagnostic_if_enabled(OsPathExists, range),
|
||||
// PTH111
|
||||
["os", "path", "expanduser"] => {
|
||||
checker.report_diagnostic_if_enabled(OsPathExpanduser, range)
|
||||
}
|
||||
// PTH112
|
||||
["os", "path", "isdir"] => checker.report_diagnostic_if_enabled(OsPathIsdir, range),
|
||||
// PTH113
|
||||
["os", "path", "isfile"] => checker.report_diagnostic_if_enabled(OsPathIsfile, range),
|
||||
// PTH114
|
||||
["os", "path", "islink"] => checker.report_diagnostic_if_enabled(OsPathIslink, range),
|
||||
|
||||
// PTH116
|
||||
["os", "stat"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
@@ -159,8 +105,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsStat, range)
|
||||
}
|
||||
// PTH117
|
||||
["os", "path", "isabs"] => checker.report_diagnostic_if_enabled(OsPathIsabs, range),
|
||||
// PTH118
|
||||
["os", "path", "join"] => checker.report_diagnostic_if_enabled(
|
||||
OsPathJoin {
|
||||
@@ -184,10 +128,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
},
|
||||
range,
|
||||
),
|
||||
// PTH119
|
||||
["os", "path", "basename"] => checker.report_diagnostic_if_enabled(OsPathBasename, range),
|
||||
// PTH120
|
||||
["os", "path", "dirname"] => checker.report_diagnostic_if_enabled(OsPathDirname, range),
|
||||
// PTH121
|
||||
["os", "path", "samefile"] => checker.report_diagnostic_if_enabled(OsPathSamefile, range),
|
||||
// PTH122
|
||||
@@ -208,7 +148,7 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
|
||||
// PTH123
|
||||
["" | "builtins", "open"] => {
|
||||
// `closefd` and `opener` are not supported by pathlib, so check if they are
|
||||
// `closefd` and `opener` are not supported by pathlib, so check if they
|
||||
// are set to non-default values.
|
||||
// https://github.com/astral-sh/ruff/issues/7620
|
||||
// Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open):
|
||||
@@ -282,20 +222,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
range,
|
||||
)
|
||||
}
|
||||
// PTH115
|
||||
// Python 3.9+
|
||||
["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => {
|
||||
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
|
||||
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.readlink)
|
||||
// ```text
|
||||
// 0 1
|
||||
// os.readlink(path, *, dir_fd=None)
|
||||
// ```
|
||||
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
|
||||
return;
|
||||
}
|
||||
checker.report_diagnostic_if_enabled(OsReadlink, range)
|
||||
}
|
||||
// PTH208
|
||||
["os", "listdir"] => {
|
||||
if call
|
||||
@@ -338,7 +264,7 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
|
||||
match expr {
|
||||
Expr::Name(name) => Some(name),
|
||||
Expr::Call(ast::ExprCall { func, .. }) => get_name_expr(func),
|
||||
Expr::Call(ExprCall { func, .. }) => get_name_expr(func),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -349,9 +275,3 @@ fn is_argument_non_default(arguments: &ast::Arguments, name: &str, position: usi
|
||||
.find_argument_value(name, position)
|
||||
.is_some_and(|expr| !expr.is_none_literal_expr())
|
||||
}
|
||||
|
||||
fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool {
|
||||
arguments
|
||||
.find_keyword(name)
|
||||
.is_some_and(|keyword| !keyword.value.is_none_literal_expr())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ full_name.py:7:5: PTH100 `os.path.abspath()` should be replaced by `Path.resolve
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
full_name.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ full_name.py:13:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
full_name.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ full_name.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
full_name.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ full_name.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
full_name.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ full_name.py:17:5: PTH110 `os.path.exists()` should be replaced by `Path.exists(
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
full_name.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ full_name.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.exp
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
full_name.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ full_name.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
full_name.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ full_name.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
full_name.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ full_name.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_syml
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
full_name.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ full_name.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
full_name.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ full_name.py:24:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_absol
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
full_name.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ full_name.py:28:1: PTH119 `os.path.basename()` should be replaced by `Path.name`
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
full_name.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ full_name.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
full_name.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -10,6 +10,7 @@ import_as.py:7:5: PTH100 `os.path.abspath()` should be replaced by `Path.resolve
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
import_as.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ import_as.py:13:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
import_as.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ import_as.py:14:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_as.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ import_as.py:15:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_as.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ import_as.py:17:5: PTH110 `os.path.exists()` should be replaced by `Path.exists(
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
import_as.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ import_as.py:18:6: PTH111 `os.path.expanduser()` should be replaced by `Path.exp
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
import_as.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ import_as.py:19:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
import_as.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ import_as.py:20:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
import_as.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ import_as.py:21:9: PTH114 `os.path.islink()` should be replaced by `Path.is_syml
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
import_as.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ import_as.py:22:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
import_as.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ import_as.py:24:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_absol
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
import_as.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ import_as.py:28:1: PTH119 `os.path.basename()` should be replaced by `Path.name`
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
import_as.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ import_as.py:29:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent
|
||||
30 | foo_p.samefile(p)
|
||||
31 | foo_p.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
import_as.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -10,6 +10,7 @@ import_from.py:9:5: PTH100 `os.path.abspath()` should be replaced by `Path.resol
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
import_from.py:10:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ import_from.py:15:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
import_from.py:16:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ import_from.py:16:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from.py:17:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ import_from.py:17:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from.py:18:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ import_from.py:19:5: PTH110 `os.path.exists()` should be replaced by `Path.exist
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
import_from.py:20:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ import_from.py:20:6: PTH111 `os.path.expanduser()` should be replaced by `Path.e
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
import_from.py:21:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ import_from.py:21:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
import_from.py:22:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ import_from.py:22:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_fi
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
import_from.py:23:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ import_from.py:23:9: PTH114 `os.path.islink()` should be replaced by `Path.is_sy
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
import_from.py:24:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ import_from.py:24:1: PTH115 `os.readlink()` should be replaced by `Path.readlink
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
import_from.py:25:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ import_from.py:26:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_abs
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
import_from.py:27:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ import_from.py:30:1: PTH119 `os.path.basename()` should be replaced by `Path.nam
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
import_from.py:31:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ import_from.py:31:1: PTH120 `os.path.dirname()` should be replaced by `Path.pare
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
import_from.py:32:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -10,6 +10,7 @@ import_from_as.py:14:5: PTH100 `os.path.abspath()` should be replaced by `Path.r
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
import_from_as.py:15:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
@@ -69,6 +70,7 @@ import_from_as.py:20:1: PTH106 `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
import_from_as.py:21:1: PTH107 `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -79,6 +81,7 @@ import_from_as.py:21:1: PTH107 `os.remove()` should be replaced by `Path.unlink(
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from_as.py:22:1: PTH108 `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
@@ -89,6 +92,7 @@ import_from_as.py:22:1: PTH108 `os.unlink()` should be replaced by `Path.unlink(
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
import_from_as.py:23:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
@@ -109,6 +113,7 @@ import_from_as.py:24:5: PTH110 `os.path.exists()` should be replaced by `Path.ex
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
import_from_as.py:25:6: PTH111 `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
@@ -119,6 +124,7 @@ import_from_as.py:25:6: PTH111 `os.path.expanduser()` should be replaced by `Pat
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
import_from_as.py:26:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
@@ -129,6 +135,7 @@ import_from_as.py:26:7: PTH112 `os.path.isdir()` should be replaced by `Path.is_
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
import_from_as.py:27:8: PTH113 `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
@@ -139,6 +146,7 @@ import_from_as.py:27:8: PTH113 `os.path.isfile()` should be replaced by `Path.is
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
import_from_as.py:28:9: PTH114 `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
@@ -149,6 +157,7 @@ import_from_as.py:28:9: PTH114 `os.path.islink()` should be replaced by `Path.is
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
import_from_as.py:29:1: PTH115 `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
@@ -159,6 +168,7 @@ import_from_as.py:29:1: PTH115 `os.readlink()` should be replaced by `Path.readl
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
import_from_as.py:30:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
@@ -179,6 +189,7 @@ import_from_as.py:31:1: PTH117 `os.path.isabs()` should be replaced by `Path.is_
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
import_from_as.py:32:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
@@ -219,6 +230,7 @@ import_from_as.py:35:1: PTH119 `os.path.basename()` should be replaced by `Path.
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
import_from_as.py:36:1: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
@@ -229,6 +241,7 @@ import_from_as.py:36:1: PTH120 `os.path.dirname()` should be replaced by `Path.p
|
||||
37 | xsamefile(p)
|
||||
38 | xsplitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
import_from_as.py:37:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
|
||||
@@ -0,0 +1,580 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
full_name.py:7:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
5 | q = "bar"
|
||||
6 |
|
||||
7 | a = os.path.abspath(p)
|
||||
| ^^^^^^^^^^^^^^^ PTH100
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
6 7 |
|
||||
7 |-a = os.path.abspath(p)
|
||||
8 |+a = pathlib.Path(p).resolve()
|
||||
8 9 | aa = os.chmod(p)
|
||||
9 10 | aaa = os.mkdir(p)
|
||||
10 11 | os.makedirs(p)
|
||||
|
||||
full_name.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
7 | a = os.path.abspath(p)
|
||||
8 | aa = os.chmod(p)
|
||||
| ^^^^^^^^ PTH101
|
||||
9 | aaa = os.mkdir(p)
|
||||
10 | os.makedirs(p)
|
||||
|
|
||||
|
||||
full_name.py:9:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
7 | a = os.path.abspath(p)
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
| ^^^^^^^^ PTH102
|
||||
10 | os.makedirs(p)
|
||||
11 | os.rename(p)
|
||||
|
|
||||
|
||||
full_name.py:10:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
8 | aa = os.chmod(p)
|
||||
9 | aaa = os.mkdir(p)
|
||||
10 | os.makedirs(p)
|
||||
| ^^^^^^^^^^^ PTH103
|
||||
11 | os.rename(p)
|
||||
12 | os.replace(p)
|
||||
|
|
||||
|
||||
full_name.py:11:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
9 | aaa = os.mkdir(p)
|
||||
10 | os.makedirs(p)
|
||||
11 | os.rename(p)
|
||||
| ^^^^^^^^^ PTH104
|
||||
12 | os.replace(p)
|
||||
13 | os.rmdir(p)
|
||||
|
|
||||
|
||||
full_name.py:12:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
10 | os.makedirs(p)
|
||||
11 | os.rename(p)
|
||||
12 | os.replace(p)
|
||||
| ^^^^^^^^^^ PTH105
|
||||
13 | os.rmdir(p)
|
||||
14 | os.remove(p)
|
||||
|
|
||||
|
||||
full_name.py:13:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
11 | os.rename(p)
|
||||
12 | os.replace(p)
|
||||
13 | os.rmdir(p)
|
||||
| ^^^^^^^^ PTH106
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
10 11 | os.makedirs(p)
|
||||
11 12 | os.rename(p)
|
||||
12 13 | os.replace(p)
|
||||
13 |-os.rmdir(p)
|
||||
14 |+pathlib.Path(p).rmdir()
|
||||
14 15 | os.remove(p)
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
|
||||
full_name.py:14:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
12 | os.replace(p)
|
||||
13 | os.rmdir(p)
|
||||
14 | os.remove(p)
|
||||
| ^^^^^^^^^ PTH107
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
11 12 | os.rename(p)
|
||||
12 13 | os.replace(p)
|
||||
13 14 | os.rmdir(p)
|
||||
14 |-os.remove(p)
|
||||
15 |+pathlib.Path(p).unlink()
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
|
||||
full_name.py:15:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
13 | os.rmdir(p)
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
| ^^^^^^^^^ PTH108
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
12 13 | os.replace(p)
|
||||
13 14 | os.rmdir(p)
|
||||
14 15 | os.remove(p)
|
||||
15 |-os.unlink(p)
|
||||
16 |+pathlib.Path(p).unlink()
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
|
||||
full_name.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
14 | os.remove(p)
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
| ^^^^^^^^^ PTH109
|
||||
17 | b = os.path.exists(p)
|
||||
18 | bb = os.path.expanduser(p)
|
||||
|
|
||||
|
||||
full_name.py:17:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
15 | os.unlink(p)
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
| ^^^^^^^^^^^^^^ PTH110
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
14 15 | os.remove(p)
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
17 |-b = os.path.exists(p)
|
||||
18 |+b = pathlib.Path(p).exists()
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
|
||||
full_name.py:18:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
16 | os.getcwd(p)
|
||||
17 | b = os.path.exists(p)
|
||||
18 | bb = os.path.expanduser(p)
|
||||
| ^^^^^^^^^^^^^^^^^^ PTH111
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
15 16 | os.unlink(p)
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 |-bb = os.path.expanduser(p)
|
||||
19 |+bb = pathlib.Path(p).expanduser()
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
|
||||
full_name.py:19:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
17 | b = os.path.exists(p)
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
| ^^^^^^^^^^^^^ PTH112
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
16 17 | os.getcwd(p)
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 |-bbb = os.path.isdir(p)
|
||||
20 |+bbb = pathlib.Path(p).is_dir()
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 23 | os.readlink(p)
|
||||
|
||||
full_name.py:20:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
18 | bb = os.path.expanduser(p)
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
| ^^^^^^^^^^^^^^ PTH113
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | b = os.path.exists(p)
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 |-bbbb = os.path.isfile(p)
|
||||
21 |+bbbb = pathlib.Path(p).is_file()
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 23 | os.readlink(p)
|
||||
23 24 | os.stat(p)
|
||||
|
||||
full_name.py:21:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
19 | bbb = os.path.isdir(p)
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
| ^^^^^^^^^^^^^^ PTH114
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | bb = os.path.expanduser(p)
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 |-bbbbb = os.path.islink(p)
|
||||
22 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
22 23 | os.readlink(p)
|
||||
23 24 | os.stat(p)
|
||||
24 25 | os.path.isabs(p)
|
||||
|
||||
full_name.py:22:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
20 | bbbb = os.path.isfile(p)
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
| ^^^^^^^^^^^ PTH115
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | bbb = os.path.isdir(p)
|
||||
20 21 | bbbb = os.path.isfile(p)
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 |-os.readlink(p)
|
||||
23 |+pathlib.Path(p).readlink()
|
||||
23 24 | os.stat(p)
|
||||
24 25 | os.path.isabs(p)
|
||||
25 26 | os.path.join(p, q)
|
||||
|
||||
full_name.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
21 | bbbbb = os.path.islink(p)
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
| ^^^^^^^ PTH116
|
||||
24 | os.path.isabs(p)
|
||||
25 | os.path.join(p, q)
|
||||
|
|
||||
|
||||
full_name.py:24:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
22 | os.readlink(p)
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
| ^^^^^^^^^^^^^ PTH117
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | bbbbb = os.path.islink(p)
|
||||
22 23 | os.readlink(p)
|
||||
23 24 | os.stat(p)
|
||||
24 |-os.path.isabs(p)
|
||||
25 |+pathlib.Path(p).is_absolute()
|
||||
25 26 | os.path.join(p, q)
|
||||
26 27 | os.sep.join([p, q])
|
||||
27 28 | os.sep.join((p, q))
|
||||
|
||||
full_name.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
23 | os.stat(p)
|
||||
24 | os.path.isabs(p)
|
||||
25 | os.path.join(p, q)
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
26 | os.sep.join([p, q])
|
||||
27 | os.sep.join((p, q))
|
||||
|
|
||||
|
||||
full_name.py:26:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
24 | os.path.isabs(p)
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
| ^^^^^^^^^^^ PTH118
|
||||
27 | os.sep.join((p, q))
|
||||
28 | os.path.basename(p)
|
||||
|
|
||||
|
||||
full_name.py:27:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
25 | os.path.join(p, q)
|
||||
26 | os.sep.join([p, q])
|
||||
27 | os.sep.join((p, q))
|
||||
| ^^^^^^^^^^^ PTH118
|
||||
28 | os.path.basename(p)
|
||||
29 | os.path.dirname(p)
|
||||
|
|
||||
|
||||
full_name.py:28:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
26 | os.sep.join([p, q])
|
||||
27 | os.sep.join((p, q))
|
||||
28 | os.path.basename(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH119
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | os.path.join(p, q)
|
||||
26 27 | os.sep.join([p, q])
|
||||
27 28 | os.sep.join((p, q))
|
||||
28 |-os.path.basename(p)
|
||||
29 |+pathlib.Path(p).name
|
||||
29 30 | os.path.dirname(p)
|
||||
30 31 | os.path.samefile(p)
|
||||
31 32 | os.path.splitext(p)
|
||||
|
||||
full_name.py:29:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
27 | os.sep.join((p, q))
|
||||
28 | os.path.basename(p)
|
||||
29 | os.path.dirname(p)
|
||||
| ^^^^^^^^^^^^^^^ PTH120
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | os.sep.join([p, q])
|
||||
27 28 | os.sep.join((p, q))
|
||||
28 29 | os.path.basename(p)
|
||||
29 |-os.path.dirname(p)
|
||||
30 |+pathlib.Path(p).parent
|
||||
30 31 | os.path.samefile(p)
|
||||
31 32 | os.path.splitext(p)
|
||||
32 33 | with open(p) as fp:
|
||||
|
||||
full_name.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
28 | os.path.basename(p)
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH121
|
||||
31 | os.path.splitext(p)
|
||||
32 | with open(p) as fp:
|
||||
|
|
||||
|
||||
full_name.py:31:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
29 | os.path.dirname(p)
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH122
|
||||
32 | with open(p) as fp:
|
||||
33 | fp.read()
|
||||
|
|
||||
|
||||
full_name.py:32:6: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
30 | os.path.samefile(p)
|
||||
31 | os.path.splitext(p)
|
||||
32 | with open(p) as fp:
|
||||
| ^^^^ PTH123
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
|
|
||||
|
||||
full_name.py:34:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
32 | with open(p) as fp:
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
| ^^^^ PTH123
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
|
|
||||
|
||||
full_name.py:35:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
33 | fp.read()
|
||||
34 | open(p).close()
|
||||
35 | os.getcwdb(p)
|
||||
| ^^^^^^^^^^ PTH109
|
||||
36 | os.path.join(p, *q)
|
||||
37 | os.sep.join(p, *q)
|
||||
|
|
||||
|
||||
full_name.py:36:1: PTH118 `os.path.join()` should be replaced by `Path.joinpath()`
|
||||
|
|
||||
34 | open(p).close()
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
37 | os.sep.join(p, *q)
|
||||
|
|
||||
|
||||
full_name.py:37:1: PTH118 `os.sep.join()` should be replaced by `Path.joinpath()`
|
||||
|
|
||||
35 | os.getcwdb(p)
|
||||
36 | os.path.join(p, *q)
|
||||
37 | os.sep.join(p, *q)
|
||||
| ^^^^^^^^^^^ PTH118
|
||||
38 |
|
||||
39 | # https://github.com/astral-sh/ruff/issues/7620
|
||||
|
|
||||
|
||||
full_name.py:46:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
44 | open(p, closefd=False)
|
||||
45 | open(p, opener=opener)
|
||||
46 | open(p, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
| ^^^^ PTH123
|
||||
47 | open(p, 'r', - 1, None, None, None, True, None)
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
|
||||
full_name.py:47:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
45 | open(p, opener=opener)
|
||||
46 | open(p, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
|
||||
47 | open(p, 'r', - 1, None, None, None, True, None)
|
||||
| ^^^^ PTH123
|
||||
48 | open(p, 'r', - 1, None, None, None, False, opener)
|
||||
|
|
||||
|
||||
full_name.py:65:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
63 | open(f())
|
||||
64 |
|
||||
65 | open(b"foo")
|
||||
| ^^^^ PTH123
|
||||
66 | byte_str = b"bar"
|
||||
67 | open(byte_str)
|
||||
|
|
||||
|
||||
full_name.py:67:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
65 | open(b"foo")
|
||||
66 | byte_str = b"bar"
|
||||
67 | open(byte_str)
|
||||
| ^^^^ PTH123
|
||||
68 |
|
||||
69 | def bytes_str_func() -> bytes:
|
||||
|
|
||||
|
||||
full_name.py:71:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
69 | def bytes_str_func() -> bytes:
|
||||
70 | return b"foo"
|
||||
71 | open(bytes_str_func())
|
||||
| ^^^^ PTH123
|
||||
72 |
|
||||
73 | # https://github.com/astral-sh/ruff/issues/17693
|
||||
|
|
||||
@@ -0,0 +1,478 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
import_as.py:7:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
5 | q = "bar"
|
||||
6 |
|
||||
7 | a = foo_p.abspath(p)
|
||||
| ^^^^^^^^^^^^^ PTH100
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
6 7 |
|
||||
7 |-a = foo_p.abspath(p)
|
||||
8 |+a = pathlib.Path(p).resolve()
|
||||
8 9 | aa = foo.chmod(p)
|
||||
9 10 | aaa = foo.mkdir(p)
|
||||
10 11 | foo.makedirs(p)
|
||||
|
||||
import_as.py:8:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
7 | a = foo_p.abspath(p)
|
||||
8 | aa = foo.chmod(p)
|
||||
| ^^^^^^^^^ PTH101
|
||||
9 | aaa = foo.mkdir(p)
|
||||
10 | foo.makedirs(p)
|
||||
|
|
||||
|
||||
import_as.py:9:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
7 | a = foo_p.abspath(p)
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
| ^^^^^^^^^ PTH102
|
||||
10 | foo.makedirs(p)
|
||||
11 | foo.rename(p)
|
||||
|
|
||||
|
||||
import_as.py:10:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
8 | aa = foo.chmod(p)
|
||||
9 | aaa = foo.mkdir(p)
|
||||
10 | foo.makedirs(p)
|
||||
| ^^^^^^^^^^^^ PTH103
|
||||
11 | foo.rename(p)
|
||||
12 | foo.replace(p)
|
||||
|
|
||||
|
||||
import_as.py:11:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
9 | aaa = foo.mkdir(p)
|
||||
10 | foo.makedirs(p)
|
||||
11 | foo.rename(p)
|
||||
| ^^^^^^^^^^ PTH104
|
||||
12 | foo.replace(p)
|
||||
13 | foo.rmdir(p)
|
||||
|
|
||||
|
||||
import_as.py:12:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
10 | foo.makedirs(p)
|
||||
11 | foo.rename(p)
|
||||
12 | foo.replace(p)
|
||||
| ^^^^^^^^^^^ PTH105
|
||||
13 | foo.rmdir(p)
|
||||
14 | foo.remove(p)
|
||||
|
|
||||
|
||||
import_as.py:13:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
11 | foo.rename(p)
|
||||
12 | foo.replace(p)
|
||||
13 | foo.rmdir(p)
|
||||
| ^^^^^^^^^ PTH106
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
10 11 | foo.makedirs(p)
|
||||
11 12 | foo.rename(p)
|
||||
12 13 | foo.replace(p)
|
||||
13 |-foo.rmdir(p)
|
||||
14 |+pathlib.Path(p).rmdir()
|
||||
14 15 | foo.remove(p)
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
|
||||
import_as.py:14:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
12 | foo.replace(p)
|
||||
13 | foo.rmdir(p)
|
||||
14 | foo.remove(p)
|
||||
| ^^^^^^^^^^ PTH107
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
11 12 | foo.rename(p)
|
||||
12 13 | foo.replace(p)
|
||||
13 14 | foo.rmdir(p)
|
||||
14 |-foo.remove(p)
|
||||
15 |+pathlib.Path(p).unlink()
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
|
||||
import_as.py:15:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
13 | foo.rmdir(p)
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
| ^^^^^^^^^^ PTH108
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
12 13 | foo.replace(p)
|
||||
13 14 | foo.rmdir(p)
|
||||
14 15 | foo.remove(p)
|
||||
15 |-foo.unlink(p)
|
||||
16 |+pathlib.Path(p).unlink()
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
|
||||
import_as.py:16:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
14 | foo.remove(p)
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
| ^^^^^^^^^^ PTH109
|
||||
17 | b = foo_p.exists(p)
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
|
|
||||
|
||||
import_as.py:17:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
15 | foo.unlink(p)
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
| ^^^^^^^^^^^^ PTH110
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
14 15 | foo.remove(p)
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
17 |-b = foo_p.exists(p)
|
||||
18 |+b = pathlib.Path(p).exists()
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
|
||||
import_as.py:18:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
16 | foo.getcwd(p)
|
||||
17 | b = foo_p.exists(p)
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
| ^^^^^^^^^^^^^^^^ PTH111
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
15 16 | foo.unlink(p)
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 |-bb = foo_p.expanduser(p)
|
||||
19 |+bb = pathlib.Path(p).expanduser()
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
|
||||
import_as.py:19:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
17 | b = foo_p.exists(p)
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
| ^^^^^^^^^^^ PTH112
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
16 17 | foo.getcwd(p)
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 |-bbb = foo_p.isdir(p)
|
||||
20 |+bbb = pathlib.Path(p).is_dir()
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 23 | foo.readlink(p)
|
||||
|
||||
import_as.py:20:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
18 | bb = foo_p.expanduser(p)
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
| ^^^^^^^^^^^^ PTH113
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | b = foo_p.exists(p)
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 |-bbbb = foo_p.isfile(p)
|
||||
21 |+bbbb = pathlib.Path(p).is_file()
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 23 | foo.readlink(p)
|
||||
23 24 | foo.stat(p)
|
||||
|
||||
import_as.py:21:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
19 | bbb = foo_p.isdir(p)
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
| ^^^^^^^^^^^^ PTH114
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | bb = foo_p.expanduser(p)
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 |-bbbbb = foo_p.islink(p)
|
||||
22 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
22 23 | foo.readlink(p)
|
||||
23 24 | foo.stat(p)
|
||||
24 25 | foo_p.isabs(p)
|
||||
|
||||
import_as.py:22:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
20 | bbbb = foo_p.isfile(p)
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
| ^^^^^^^^^^^^ PTH115
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | bbb = foo_p.isdir(p)
|
||||
20 21 | bbbb = foo_p.isfile(p)
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 |-foo.readlink(p)
|
||||
23 |+pathlib.Path(p).readlink()
|
||||
23 24 | foo.stat(p)
|
||||
24 25 | foo_p.isabs(p)
|
||||
25 26 | foo_p.join(p, q)
|
||||
|
||||
import_as.py:23:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
21 | bbbbb = foo_p.islink(p)
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
| ^^^^^^^^ PTH116
|
||||
24 | foo_p.isabs(p)
|
||||
25 | foo_p.join(p, q)
|
||||
|
|
||||
|
||||
import_as.py:24:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
22 | foo.readlink(p)
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
| ^^^^^^^^^^^ PTH117
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | bbbbb = foo_p.islink(p)
|
||||
22 23 | foo.readlink(p)
|
||||
23 24 | foo.stat(p)
|
||||
24 |-foo_p.isabs(p)
|
||||
25 |+pathlib.Path(p).is_absolute()
|
||||
25 26 | foo_p.join(p, q)
|
||||
26 27 | foo.sep.join([p, q])
|
||||
27 28 | foo.sep.join((p, q))
|
||||
|
||||
import_as.py:25:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
23 | foo.stat(p)
|
||||
24 | foo_p.isabs(p)
|
||||
25 | foo_p.join(p, q)
|
||||
| ^^^^^^^^^^ PTH118
|
||||
26 | foo.sep.join([p, q])
|
||||
27 | foo.sep.join((p, q))
|
||||
|
|
||||
|
||||
import_as.py:26:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
24 | foo_p.isabs(p)
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
27 | foo.sep.join((p, q))
|
||||
28 | foo_p.basename(p)
|
||||
|
|
||||
|
||||
import_as.py:27:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
25 | foo_p.join(p, q)
|
||||
26 | foo.sep.join([p, q])
|
||||
27 | foo.sep.join((p, q))
|
||||
| ^^^^^^^^^^^^ PTH118
|
||||
28 | foo_p.basename(p)
|
||||
29 | foo_p.dirname(p)
|
||||
|
|
||||
|
||||
import_as.py:28:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
26 | foo.sep.join([p, q])
|
||||
27 | foo.sep.join((p, q))
|
||||
28 | foo_p.basename(p)
|
||||
| ^^^^^^^^^^^^^^ PTH119
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | foo_p.join(p, q)
|
||||
26 27 | foo.sep.join([p, q])
|
||||
27 28 | foo.sep.join((p, q))
|
||||
28 |-foo_p.basename(p)
|
||||
29 |+pathlib.Path(p).name
|
||||
29 30 | foo_p.dirname(p)
|
||||
30 31 | foo_p.samefile(p)
|
||||
31 32 | foo_p.splitext(p)
|
||||
|
||||
import_as.py:29:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
27 | foo.sep.join((p, q))
|
||||
28 | foo_p.basename(p)
|
||||
29 | foo_p.dirname(p)
|
||||
| ^^^^^^^^^^^^^ PTH120
|
||||
30 | foo_p.samefile(p)
|
||||
31 | foo_p.splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
3 4 |
|
||||
4 5 | p = "/foo"
|
||||
5 6 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | foo.sep.join([p, q])
|
||||
27 28 | foo.sep.join((p, q))
|
||||
28 29 | foo_p.basename(p)
|
||||
29 |-foo_p.dirname(p)
|
||||
30 |+pathlib.Path(p).parent
|
||||
30 31 | foo_p.samefile(p)
|
||||
31 32 | foo_p.splitext(p)
|
||||
|
||||
import_as.py:30:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
28 | foo_p.basename(p)
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
| ^^^^^^^^^^^^^^ PTH121
|
||||
31 | foo_p.splitext(p)
|
||||
|
|
||||
|
||||
import_as.py:31:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
29 | foo_p.dirname(p)
|
||||
30 | foo_p.samefile(p)
|
||||
31 | foo_p.splitext(p)
|
||||
| ^^^^^^^^^^^^^^ PTH122
|
||||
|
|
||||
@@ -0,0 +1,521 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
import_from.py:9:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
7 | q = "bar"
|
||||
8 |
|
||||
9 | a = abspath(p)
|
||||
| ^^^^^^^ PTH100
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
8 9 |
|
||||
9 |-a = abspath(p)
|
||||
10 |+a = pathlib.Path(p).resolve()
|
||||
10 11 | aa = chmod(p)
|
||||
11 12 | aaa = mkdir(p)
|
||||
12 13 | makedirs(p)
|
||||
|
||||
import_from.py:10:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
9 | a = abspath(p)
|
||||
10 | aa = chmod(p)
|
||||
| ^^^^^ PTH101
|
||||
11 | aaa = mkdir(p)
|
||||
12 | makedirs(p)
|
||||
|
|
||||
|
||||
import_from.py:11:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
9 | a = abspath(p)
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
| ^^^^^ PTH102
|
||||
12 | makedirs(p)
|
||||
13 | rename(p)
|
||||
|
|
||||
|
||||
import_from.py:12:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
10 | aa = chmod(p)
|
||||
11 | aaa = mkdir(p)
|
||||
12 | makedirs(p)
|
||||
| ^^^^^^^^ PTH103
|
||||
13 | rename(p)
|
||||
14 | replace(p)
|
||||
|
|
||||
|
||||
import_from.py:13:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
11 | aaa = mkdir(p)
|
||||
12 | makedirs(p)
|
||||
13 | rename(p)
|
||||
| ^^^^^^ PTH104
|
||||
14 | replace(p)
|
||||
15 | rmdir(p)
|
||||
|
|
||||
|
||||
import_from.py:14:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
12 | makedirs(p)
|
||||
13 | rename(p)
|
||||
14 | replace(p)
|
||||
| ^^^^^^^ PTH105
|
||||
15 | rmdir(p)
|
||||
16 | remove(p)
|
||||
|
|
||||
|
||||
import_from.py:15:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
13 | rename(p)
|
||||
14 | replace(p)
|
||||
15 | rmdir(p)
|
||||
| ^^^^^ PTH106
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
12 13 | makedirs(p)
|
||||
13 14 | rename(p)
|
||||
14 15 | replace(p)
|
||||
15 |-rmdir(p)
|
||||
16 |+pathlib.Path(p).rmdir()
|
||||
16 17 | remove(p)
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
|
||||
import_from.py:16:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
14 | replace(p)
|
||||
15 | rmdir(p)
|
||||
16 | remove(p)
|
||||
| ^^^^^^ PTH107
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
13 14 | rename(p)
|
||||
14 15 | replace(p)
|
||||
15 16 | rmdir(p)
|
||||
16 |-remove(p)
|
||||
17 |+pathlib.Path(p).unlink()
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
|
||||
import_from.py:17:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
15 | rmdir(p)
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
| ^^^^^^ PTH108
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
14 15 | replace(p)
|
||||
15 16 | rmdir(p)
|
||||
16 17 | remove(p)
|
||||
17 |-unlink(p)
|
||||
18 |+pathlib.Path(p).unlink()
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
20 21 | bb = expanduser(p)
|
||||
|
||||
import_from.py:18:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
16 | remove(p)
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
| ^^^^^^ PTH109
|
||||
19 | b = exists(p)
|
||||
20 | bb = expanduser(p)
|
||||
|
|
||||
|
||||
import_from.py:19:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
17 | unlink(p)
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
| ^^^^^^ PTH110
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
16 17 | remove(p)
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
19 |-b = exists(p)
|
||||
20 |+b = pathlib.Path(p).exists()
|
||||
20 21 | bb = expanduser(p)
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
|
||||
import_from.py:20:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
18 | getcwd(p)
|
||||
19 | b = exists(p)
|
||||
20 | bb = expanduser(p)
|
||||
| ^^^^^^^^^^ PTH111
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | unlink(p)
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
20 |-bb = expanduser(p)
|
||||
21 |+bb = pathlib.Path(p).expanduser()
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 24 | bbbbb = islink(p)
|
||||
|
||||
import_from.py:21:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
19 | b = exists(p)
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
| ^^^^^ PTH112
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | getcwd(p)
|
||||
19 20 | b = exists(p)
|
||||
20 21 | bb = expanduser(p)
|
||||
21 |-bbb = isdir(p)
|
||||
22 |+bbb = pathlib.Path(p).is_dir()
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 25 | readlink(p)
|
||||
|
||||
import_from.py:22:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
20 | bb = expanduser(p)
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
| ^^^^^^ PTH113
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | b = exists(p)
|
||||
20 21 | bb = expanduser(p)
|
||||
21 22 | bbb = isdir(p)
|
||||
22 |-bbbb = isfile(p)
|
||||
23 |+bbbb = pathlib.Path(p).is_file()
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 25 | readlink(p)
|
||||
25 26 | stat(p)
|
||||
|
||||
import_from.py:23:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
21 | bbb = isdir(p)
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
| ^^^^^^ PTH114
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
20 21 | bb = expanduser(p)
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 |-bbbbb = islink(p)
|
||||
24 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
24 25 | readlink(p)
|
||||
25 26 | stat(p)
|
||||
26 27 | isabs(p)
|
||||
|
||||
import_from.py:24:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
22 | bbbb = isfile(p)
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
| ^^^^^^^^ PTH115
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | bbb = isdir(p)
|
||||
22 23 | bbbb = isfile(p)
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 |-readlink(p)
|
||||
25 |+pathlib.Path(p).readlink()
|
||||
25 26 | stat(p)
|
||||
26 27 | isabs(p)
|
||||
27 28 | join(p, q)
|
||||
|
||||
import_from.py:25:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
23 | bbbbb = islink(p)
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
| ^^^^ PTH116
|
||||
26 | isabs(p)
|
||||
27 | join(p, q)
|
||||
|
|
||||
|
||||
import_from.py:26:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
24 | readlink(p)
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
| ^^^^^ PTH117
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
23 24 | bbbbb = islink(p)
|
||||
24 25 | readlink(p)
|
||||
25 26 | stat(p)
|
||||
26 |-isabs(p)
|
||||
27 |+pathlib.Path(p).is_absolute()
|
||||
27 28 | join(p, q)
|
||||
28 29 | sep.join((p, q))
|
||||
29 30 | sep.join([p, q])
|
||||
|
||||
import_from.py:27:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
25 | stat(p)
|
||||
26 | isabs(p)
|
||||
27 | join(p, q)
|
||||
| ^^^^ PTH118
|
||||
28 | sep.join((p, q))
|
||||
29 | sep.join([p, q])
|
||||
|
|
||||
|
||||
import_from.py:28:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
26 | isabs(p)
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
| ^^^^^^^^ PTH118
|
||||
29 | sep.join([p, q])
|
||||
30 | basename(p)
|
||||
|
|
||||
|
||||
import_from.py:29:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
27 | join(p, q)
|
||||
28 | sep.join((p, q))
|
||||
29 | sep.join([p, q])
|
||||
| ^^^^^^^^ PTH118
|
||||
30 | basename(p)
|
||||
31 | dirname(p)
|
||||
|
|
||||
|
||||
import_from.py:30:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
28 | sep.join((p, q))
|
||||
29 | sep.join([p, q])
|
||||
30 | basename(p)
|
||||
| ^^^^^^^^ PTH119
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
27 28 | join(p, q)
|
||||
28 29 | sep.join((p, q))
|
||||
29 30 | sep.join([p, q])
|
||||
30 |-basename(p)
|
||||
31 |+pathlib.Path(p).name
|
||||
31 32 | dirname(p)
|
||||
32 33 | samefile(p)
|
||||
33 34 | splitext(p)
|
||||
|
||||
import_from.py:31:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
29 | sep.join([p, q])
|
||||
30 | basename(p)
|
||||
31 | dirname(p)
|
||||
| ^^^^^^^ PTH120
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
5 |+import pathlib
|
||||
5 6 |
|
||||
6 7 | p = "/foo"
|
||||
7 8 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
28 29 | sep.join((p, q))
|
||||
29 30 | sep.join([p, q])
|
||||
30 31 | basename(p)
|
||||
31 |-dirname(p)
|
||||
32 |+pathlib.Path(p).parent
|
||||
32 33 | samefile(p)
|
||||
33 34 | splitext(p)
|
||||
34 35 | with open(p) as fp:
|
||||
|
||||
import_from.py:32:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
30 | basename(p)
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
| ^^^^^^^^ PTH121
|
||||
33 | splitext(p)
|
||||
34 | with open(p) as fp:
|
||||
|
|
||||
|
||||
import_from.py:33:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
31 | dirname(p)
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
| ^^^^^^^^ PTH122
|
||||
34 | with open(p) as fp:
|
||||
35 | fp.read()
|
||||
|
|
||||
|
||||
import_from.py:34:6: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
32 | samefile(p)
|
||||
33 | splitext(p)
|
||||
34 | with open(p) as fp:
|
||||
| ^^^^ PTH123
|
||||
35 | fp.read()
|
||||
36 | open(p).close()
|
||||
|
|
||||
|
||||
import_from.py:36:1: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
34 | with open(p) as fp:
|
||||
35 | fp.read()
|
||||
36 | open(p).close()
|
||||
| ^^^^ PTH123
|
||||
|
|
||||
|
||||
import_from.py:43:10: PTH123 `open()` should be replaced by `Path.open()`
|
||||
|
|
||||
41 | from builtins import open
|
||||
42 |
|
||||
43 | with open(p) as _: ... # Error
|
||||
| ^^^^ PTH123
|
||||
|
|
||||
@@ -0,0 +1,491 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
import_from_as.py:14:5: PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
12 | q = "bar"
|
||||
13 |
|
||||
14 | a = xabspath(p)
|
||||
| ^^^^^^^^ PTH100
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
13 14 |
|
||||
14 |-a = xabspath(p)
|
||||
15 |+a = pathlib.Path(p).resolve()
|
||||
15 16 | aa = xchmod(p)
|
||||
16 17 | aaa = xmkdir(p)
|
||||
17 18 | xmakedirs(p)
|
||||
|
||||
import_from_as.py:15:6: PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
||||
|
|
||||
14 | a = xabspath(p)
|
||||
15 | aa = xchmod(p)
|
||||
| ^^^^^^ PTH101
|
||||
16 | aaa = xmkdir(p)
|
||||
17 | xmakedirs(p)
|
||||
|
|
||||
|
||||
import_from_as.py:16:7: PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
|
||||
|
|
||||
14 | a = xabspath(p)
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
| ^^^^^^ PTH102
|
||||
17 | xmakedirs(p)
|
||||
18 | xrename(p)
|
||||
|
|
||||
|
||||
import_from_as.py:17:1: PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
|
||||
|
|
||||
15 | aa = xchmod(p)
|
||||
16 | aaa = xmkdir(p)
|
||||
17 | xmakedirs(p)
|
||||
| ^^^^^^^^^ PTH103
|
||||
18 | xrename(p)
|
||||
19 | xreplace(p)
|
||||
|
|
||||
|
||||
import_from_as.py:18:1: PTH104 `os.rename()` should be replaced by `Path.rename()`
|
||||
|
|
||||
16 | aaa = xmkdir(p)
|
||||
17 | xmakedirs(p)
|
||||
18 | xrename(p)
|
||||
| ^^^^^^^ PTH104
|
||||
19 | xreplace(p)
|
||||
20 | xrmdir(p)
|
||||
|
|
||||
|
||||
import_from_as.py:19:1: PTH105 `os.replace()` should be replaced by `Path.replace()`
|
||||
|
|
||||
17 | xmakedirs(p)
|
||||
18 | xrename(p)
|
||||
19 | xreplace(p)
|
||||
| ^^^^^^^^ PTH105
|
||||
20 | xrmdir(p)
|
||||
21 | xremove(p)
|
||||
|
|
||||
|
||||
import_from_as.py:20:1: PTH106 [*] `os.rmdir()` should be replaced by `Path.rmdir()`
|
||||
|
|
||||
18 | xrename(p)
|
||||
19 | xreplace(p)
|
||||
20 | xrmdir(p)
|
||||
| ^^^^^^ PTH106
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).rmdir()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | xmakedirs(p)
|
||||
18 19 | xrename(p)
|
||||
19 20 | xreplace(p)
|
||||
20 |-xrmdir(p)
|
||||
21 |+pathlib.Path(p).rmdir()
|
||||
21 22 | xremove(p)
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
|
||||
import_from_as.py:21:1: PTH107 [*] `os.remove()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
19 | xreplace(p)
|
||||
20 | xrmdir(p)
|
||||
21 | xremove(p)
|
||||
| ^^^^^^^ PTH107
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
18 19 | xrename(p)
|
||||
19 20 | xreplace(p)
|
||||
20 21 | xrmdir(p)
|
||||
21 |-xremove(p)
|
||||
22 |+pathlib.Path(p).unlink()
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
|
||||
import_from_as.py:22:1: PTH108 [*] `os.unlink()` should be replaced by `Path.unlink()`
|
||||
|
|
||||
20 | xrmdir(p)
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
| ^^^^^^^ PTH108
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
|
|
||||
= help: Replace with `Path(...).unlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | xreplace(p)
|
||||
20 21 | xrmdir(p)
|
||||
21 22 | xremove(p)
|
||||
22 |-xunlink(p)
|
||||
23 |+pathlib.Path(p).unlink()
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
25 26 | bb = xexpanduser(p)
|
||||
|
||||
import_from_as.py:23:1: PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
|
||||
|
|
||||
21 | xremove(p)
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
| ^^^^^^^ PTH109
|
||||
24 | b = xexists(p)
|
||||
25 | bb = xexpanduser(p)
|
||||
|
|
||||
|
||||
import_from_as.py:24:5: PTH110 [*] `os.path.exists()` should be replaced by `Path.exists()`
|
||||
|
|
||||
22 | xunlink(p)
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
| ^^^^^^^ PTH110
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
|
|
||||
= help: Replace with `Path(...).exists()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | xremove(p)
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
24 |-b = xexists(p)
|
||||
25 |+b = pathlib.Path(p).exists()
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
|
||||
import_from_as.py:25:6: PTH111 [*] `os.path.expanduser()` should be replaced by `Path.expanduser()`
|
||||
|
|
||||
23 | xgetcwd(p)
|
||||
24 | b = xexists(p)
|
||||
25 | bb = xexpanduser(p)
|
||||
| ^^^^^^^^^^^ PTH111
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).expanduser()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
22 23 | xunlink(p)
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
25 |-bb = xexpanduser(p)
|
||||
26 |+bb = pathlib.Path(p).expanduser()
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 29 | bbbbb = xislink(p)
|
||||
|
||||
import_from_as.py:26:7: PTH112 [*] `os.path.isdir()` should be replaced by `Path.is_dir()`
|
||||
|
|
||||
24 | b = xexists(p)
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
| ^^^^^^ PTH112
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_dir()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
23 24 | xgetcwd(p)
|
||||
24 25 | b = xexists(p)
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 |-bbb = xisdir(p)
|
||||
27 |+bbb = pathlib.Path(p).is_dir()
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 30 | xreadlink(p)
|
||||
|
||||
import_from_as.py:27:8: PTH113 [*] `os.path.isfile()` should be replaced by `Path.is_file()`
|
||||
|
|
||||
25 | bb = xexpanduser(p)
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
| ^^^^^^^ PTH113
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_file()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
24 25 | b = xexists(p)
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 |-bbbb = xisfile(p)
|
||||
28 |+bbbb = pathlib.Path(p).is_file()
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 30 | xreadlink(p)
|
||||
30 31 | xstat(p)
|
||||
|
||||
import_from_as.py:28:9: PTH114 [*] `os.path.islink()` should be replaced by `Path.is_symlink()`
|
||||
|
|
||||
26 | bbb = xisdir(p)
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
| ^^^^^^^ PTH114
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
|
|
||||
= help: Replace with `Path(...).is_symlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | bb = xexpanduser(p)
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 |-bbbbb = xislink(p)
|
||||
29 |+bbbbb = pathlib.Path(p).is_symlink()
|
||||
29 30 | xreadlink(p)
|
||||
30 31 | xstat(p)
|
||||
31 32 | xisabs(p)
|
||||
|
||||
import_from_as.py:29:1: PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()`
|
||||
|
|
||||
27 | bbbb = xisfile(p)
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
| ^^^^^^^^^ PTH115
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
|
|
||||
= help: Replace with `Path(...).readlink()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | bbb = xisdir(p)
|
||||
27 28 | bbbb = xisfile(p)
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 |-xreadlink(p)
|
||||
30 |+pathlib.Path(p).readlink()
|
||||
30 31 | xstat(p)
|
||||
31 32 | xisabs(p)
|
||||
32 33 | xjoin(p, q)
|
||||
|
||||
import_from_as.py:30:1: PTH116 `os.stat()` should be replaced by `Path.stat()`, `Path.owner()`, or `Path.group()`
|
||||
|
|
||||
28 | bbbbb = xislink(p)
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
| ^^^^^ PTH116
|
||||
31 | xisabs(p)
|
||||
32 | xjoin(p, q)
|
||||
|
|
||||
|
||||
import_from_as.py:31:1: PTH117 [*] `os.path.isabs()` should be replaced by `Path.is_absolute()`
|
||||
|
|
||||
29 | xreadlink(p)
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
| ^^^^^^ PTH117
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
|
|
||||
= help: Replace with `Path(...).is_absolute()`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
28 29 | bbbbb = xislink(p)
|
||||
29 30 | xreadlink(p)
|
||||
30 31 | xstat(p)
|
||||
31 |-xisabs(p)
|
||||
32 |+pathlib.Path(p).is_absolute()
|
||||
32 33 | xjoin(p, q)
|
||||
33 34 | s.join((p, q))
|
||||
34 35 | s.join([p, q])
|
||||
|
||||
import_from_as.py:32:1: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
30 | xstat(p)
|
||||
31 | xisabs(p)
|
||||
32 | xjoin(p, q)
|
||||
| ^^^^^ PTH118
|
||||
33 | s.join((p, q))
|
||||
34 | s.join([p, q])
|
||||
|
|
||||
|
||||
import_from_as.py:33:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
31 | xisabs(p)
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
| ^^^^^^ PTH118
|
||||
34 | s.join([p, q])
|
||||
35 | xbasename(p)
|
||||
|
|
||||
|
||||
import_from_as.py:34:1: PTH118 `os.sep.join()` should be replaced by `Path` with `/` operator
|
||||
|
|
||||
32 | xjoin(p, q)
|
||||
33 | s.join((p, q))
|
||||
34 | s.join([p, q])
|
||||
| ^^^^^^ PTH118
|
||||
35 | xbasename(p)
|
||||
36 | xdirname(p)
|
||||
|
|
||||
|
||||
import_from_as.py:35:1: PTH119 [*] `os.path.basename()` should be replaced by `Path.name`
|
||||
|
|
||||
33 | s.join((p, q))
|
||||
34 | s.join([p, q])
|
||||
35 | xbasename(p)
|
||||
| ^^^^^^^^^ PTH119
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
|
|
||||
= help: Replace with `Path(...).name`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
32 33 | xjoin(p, q)
|
||||
33 34 | s.join((p, q))
|
||||
34 35 | s.join([p, q])
|
||||
35 |-xbasename(p)
|
||||
36 |+pathlib.Path(p).name
|
||||
36 37 | xdirname(p)
|
||||
37 38 | xsamefile(p)
|
||||
38 39 | xsplitext(p)
|
||||
|
||||
import_from_as.py:36:1: PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
|
|
||||
34 | s.join([p, q])
|
||||
35 | xbasename(p)
|
||||
36 | xdirname(p)
|
||||
| ^^^^^^^^ PTH120
|
||||
37 | xsamefile(p)
|
||||
38 | xsplitext(p)
|
||||
|
|
||||
= help: Replace with `Path(...).parent`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
10 |+import pathlib
|
||||
10 11 |
|
||||
11 12 | p = "/foo"
|
||||
12 13 | q = "bar"
|
||||
--------------------------------------------------------------------------------
|
||||
33 34 | s.join((p, q))
|
||||
34 35 | s.join([p, q])
|
||||
35 36 | xbasename(p)
|
||||
36 |-xdirname(p)
|
||||
37 |+pathlib.Path(p).parent
|
||||
37 38 | xsamefile(p)
|
||||
38 39 | xsplitext(p)
|
||||
|
||||
import_from_as.py:37:1: PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
|
|
||||
35 | xbasename(p)
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
| ^^^^^^^^^ PTH121
|
||||
38 | xsplitext(p)
|
||||
|
|
||||
|
||||
import_from_as.py:38:1: PTH122 `os.path.splitext()` should be replaced by `Path.suffix`, `Path.stem`, and `Path.parent`
|
||||
|
|
||||
36 | xdirname(p)
|
||||
37 | xsamefile(p)
|
||||
38 | xsplitext(p)
|
||||
| ^^^^^^^^^ PTH122
|
||||
|
|
||||
@@ -2,51 +2,6 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use crate::Violation;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.abspath`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.resolve()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.abspath()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// file_path = os.path.abspath("../path/to/file")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// file_path = Path("../path/to/file").resolve()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
|
||||
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathAbspath;
|
||||
|
||||
impl Violation for OsPathAbspath {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.abspath()` should be replaced by `Path.resolve()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.chmod`.
|
||||
///
|
||||
@@ -275,141 +230,6 @@ impl Violation for OsReplace {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.rmdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.rmdir()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.rmdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.rmdir("folder/")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("folder/").rmdir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
|
||||
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRmdir;
|
||||
|
||||
impl Violation for OsRmdir {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.rmdir()` should be replaced by `Path.rmdir()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.remove`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.remove()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.remove("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsRemove;
|
||||
|
||||
impl Violation for OsRemove {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.remove()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.unlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.unlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.unlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.unlink("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").unlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsUnlink;
|
||||
|
||||
impl Violation for OsUnlink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.unlink()` should be replaced by `Path.unlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.getcwd` and `os.getcwdb`.
|
||||
///
|
||||
@@ -456,276 +276,6 @@ impl Violation for OsGetcwd {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.exists`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.exists()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.exists()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.exists("file.py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("file.py").exists()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
|
||||
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExists;
|
||||
|
||||
impl Violation for OsPathExists {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.exists()` should be replaced by `Path.exists()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.expanduser`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.expanduser()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.expanduser()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.expanduser("~/films/Monty Python")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("~/films/Monty Python").expanduser()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathExpanduser;
|
||||
|
||||
impl Violation for OsPathExpanduser {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.expanduser()` should be replaced by `Path.expanduser()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isdir`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_dir()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isdir()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isdir("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_dir()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
|
||||
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsdir;
|
||||
|
||||
impl Violation for OsPathIsdir {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isdir()` should be replaced by `Path.is_dir()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isfile`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_file()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.isfile()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.isfile("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_file()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
|
||||
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsfile;
|
||||
|
||||
impl Violation for OsPathIsfile {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isfile()` should be replaced by `Path.is_file()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.islink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_symlink()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.islink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.islink("docs")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path("docs").is_symlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIslink;
|
||||
|
||||
impl Violation for OsPathIslink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.islink()` should be replaced by `Path.is_symlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.readlink`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os`. When possible, using `Path` object
|
||||
/// methods such as `Path.readlink()` can improve readability over the `os`
|
||||
/// module's counterparts (e.g., `os.readlink()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.readlink(file_name)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(file_name).readlink()
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
|
||||
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsReadlink;
|
||||
|
||||
impl Violation for OsReadlink {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.readlink()` should be replaced by `Path.readlink()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.stat`.
|
||||
///
|
||||
@@ -781,53 +331,6 @@ impl Violation for OsStat {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isabs`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.is_absolute()` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., as `os.path.isabs()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// if os.path.isabs(file_name):
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// if Path(file_name).is_absolute():
|
||||
/// print("Absolute path!")
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
|
||||
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathIsabs;
|
||||
|
||||
impl Violation for OsPathIsabs {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.isabs()` should be replaced by `Path.is_absolute()`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.join`.
|
||||
///
|
||||
@@ -890,96 +393,6 @@ pub(crate) enum Joiner {
|
||||
Joinpath,
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.basename`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.name` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.basename()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.basename(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).name
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathBasename;
|
||||
|
||||
impl Violation for OsPathBasename {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.basename()` should be replaced by `Path.name`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.dirname`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pathlib` offers a high-level API for path manipulation, as compared to
|
||||
/// the lower-level API offered by `os.path`. When possible, using `Path` object
|
||||
/// methods such as `Path.parent` can improve readability over the `os.path`
|
||||
/// module's counterparts (e.g., `os.path.dirname()`).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
/// os.path.dirname(__file__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// Path(__file__).parent
|
||||
/// ```
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
|
||||
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct OsPathDirname;
|
||||
|
||||
impl Violation for OsPathDirname {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.dirname()` should be replaced by `Path.parent`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.samefile`.
|
||||
///
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_safe_super_call_with_parameters_fix_enabled;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `super` calls that pass redundant arguments.
|
||||
@@ -57,14 +57,16 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct SuperCallWithParameters;
|
||||
|
||||
impl AlwaysFixableViolation for SuperCallWithParameters {
|
||||
impl Violation for SuperCallWithParameters {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove `super()` parameters".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove `super()` parameters".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,22 +167,26 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
|
||||
return;
|
||||
}
|
||||
|
||||
let applicability = if !checker.comment_ranges().intersects(call.arguments.range())
|
||||
&& is_safe_super_call_with_parameters_fix_enabled(checker.settings())
|
||||
{
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(SuperCallWithParameters, call.arguments.range());
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::deletion(
|
||||
call.arguments.start() + TextSize::new(1),
|
||||
call.arguments.end() - TextSize::new(1),
|
||||
),
|
||||
applicability,
|
||||
));
|
||||
|
||||
// Only provide a fix if there are no keyword arguments, since super() doesn't accept keyword arguments
|
||||
if call.arguments.keywords.is_empty() {
|
||||
let applicability = if !checker.comment_ranges().intersects(call.arguments.range())
|
||||
&& is_safe_super_call_with_parameters_fix_enabled(checker.settings())
|
||||
{
|
||||
Applicability::Safe
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
};
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
Edit::deletion(
|
||||
call.arguments.start() + TextSize::new(1),
|
||||
call.arguments.end() - TextSize::new(1),
|
||||
),
|
||||
applicability,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a call is an argumented `super` invocation.
|
||||
|
||||
@@ -249,3 +249,53 @@ UP008.py:123:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
126 |- # also a comment
|
||||
127 |- ).f()
|
||||
123 |+ super().f()
|
||||
128 124 |
|
||||
129 125 |
|
||||
130 126 | # Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed
|
||||
|
||||
UP008.py:133:21: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
131 | class Ord(int):
|
||||
132 | def __len__(self):
|
||||
133 | return super(Ord, self, uhoh=True, **{"error": True}).bit_length()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
134 |
|
||||
135 | class ExampleWithKeywords:
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:137:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
135 | class ExampleWithKeywords:
|
||||
136 | def method1(self):
|
||||
137 | super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
138 |
|
||||
139 | def method2(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:140:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
139 | def method2(self):
|
||||
140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
141 |
|
||||
142 | def method3(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:143:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
142 | def method3(self):
|
||||
143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
ℹ Unsafe fix
|
||||
140 140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
141 141 |
|
||||
142 142 | def method3(self):
|
||||
143 |- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
143 |+ super().some_method() # Should be fixed - no keywords
|
||||
|
||||
@@ -249,3 +249,53 @@ UP008.py:123:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
126 |- # also a comment
|
||||
127 |- ).f()
|
||||
123 |+ super().f()
|
||||
128 124 |
|
||||
129 125 |
|
||||
130 126 | # Issue #19096: super calls with keyword arguments should emit diagnostic but not be fixed
|
||||
|
||||
UP008.py:133:21: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
131 | class Ord(int):
|
||||
132 | def __len__(self):
|
||||
133 | return super(Ord, self, uhoh=True, **{"error": True}).bit_length()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
134 |
|
||||
135 | class ExampleWithKeywords:
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:137:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
135 | class ExampleWithKeywords:
|
||||
136 | def method1(self):
|
||||
137 | super(ExampleWithKeywords, self, invalid=True).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
138 |
|
||||
139 | def method2(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:140:14: UP008 Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
139 | def method2(self):
|
||||
140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
141 |
|
||||
142 | def method3(self):
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
UP008.py:143:14: UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
|
|
||||
142 | def method3(self):
|
||||
143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP008
|
||||
|
|
||||
= help: Remove `super()` parameters
|
||||
|
||||
ℹ Safe fix
|
||||
140 140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
141 141 |
|
||||
142 142 | def method3(self):
|
||||
143 |- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
143 |+ super().some_method() # Should be fixed - no keywords
|
||||
|
||||
@@ -952,6 +952,9 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration { name, start: _ } => {
|
||||
write!(f, "name `{name}` is used prior to global declaration")
|
||||
}
|
||||
SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { name, start: _ } => {
|
||||
write!(f, "name `{name}` is used prior to nonlocal declaration")
|
||||
}
|
||||
SemanticSyntaxErrorKind::InvalidStarExpression => {
|
||||
f.write_str("Starred expression cannot be used here")
|
||||
}
|
||||
@@ -977,6 +980,18 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
write!(f, "nonlocal declaration not allowed at module level")
|
||||
}
|
||||
SemanticSyntaxErrorKind::NonlocalAndGlobal(name) => {
|
||||
write!(f, "name `{name}` is nonlocal and global")
|
||||
}
|
||||
SemanticSyntaxErrorKind::AnnotatedGlobal(name) => {
|
||||
write!(f, "annotated name `{name}` can't be global")
|
||||
}
|
||||
SemanticSyntaxErrorKind::AnnotatedNonlocal(name) => {
|
||||
write!(f, "annotated name `{name}` can't be nonlocal")
|
||||
}
|
||||
SemanticSyntaxErrorKind::InvalidNonlocal(name) => {
|
||||
write!(f, "no binding for nonlocal `{name}` found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1207,6 +1222,24 @@ pub enum SemanticSyntaxErrorKind {
|
||||
/// [#111123]: https://github.com/python/cpython/issues/111123
|
||||
LoadBeforeGlobalDeclaration { name: String, start: TextSize },
|
||||
|
||||
/// Represents the use of a `nonlocal` variable before its `nonlocal` declaration.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// counter = 0
|
||||
/// def increment():
|
||||
/// print(f"Adding 1 to {counter}")
|
||||
/// nonlocal counter # SyntaxError: name 'counter' is used prior to nonlocal declaration
|
||||
/// counter += 1
|
||||
/// ```
|
||||
///
|
||||
/// ## Known Issues
|
||||
///
|
||||
/// See [`LoadBeforeGlobalDeclaration`][Self::LoadBeforeGlobalDeclaration].
|
||||
LoadBeforeNonlocalDeclaration { name: String, start: TextSize },
|
||||
|
||||
/// Represents the use of a starred expression in an invalid location, such as a `return` or
|
||||
/// `yield` statement.
|
||||
///
|
||||
@@ -1307,6 +1340,41 @@ pub enum SemanticSyntaxErrorKind {
|
||||
|
||||
/// Represents a nonlocal declaration at module level
|
||||
NonlocalDeclarationAtModuleLevel,
|
||||
|
||||
/// Represents the same variable declared as both nonlocal and global
|
||||
NonlocalAndGlobal(String),
|
||||
|
||||
/// Represents a type annotation on a variable that's been declared global
|
||||
AnnotatedGlobal(String),
|
||||
|
||||
/// Represents a type annotation on a variable that's been declared nonlocal
|
||||
AnnotatedNonlocal(String),
|
||||
|
||||
/// Represents a nonlocal declaration with no definition in an enclosing scope
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```python
|
||||
/// def f():
|
||||
/// nonlocal x # error
|
||||
///
|
||||
/// # Global variables don't count.
|
||||
/// x = 1
|
||||
/// def f():
|
||||
/// nonlocal x # error
|
||||
///
|
||||
/// def f():
|
||||
/// x = 1
|
||||
/// def g():
|
||||
/// nonlocal x # allowed
|
||||
///
|
||||
/// # The definition can come later.
|
||||
/// def f():
|
||||
/// def g():
|
||||
/// nonlocal x # allowed
|
||||
/// x = 1
|
||||
/// ```
|
||||
InvalidNonlocal(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
|
||||
@@ -26,7 +26,6 @@ argfile = { workspace = true }
|
||||
clap = { workspace = true, features = ["wrap_help", "string", "env"] }
|
||||
clap_complete_command = { workspace = true }
|
||||
colored = { workspace = true }
|
||||
countme = { workspace = true, features = ["enable"] }
|
||||
crossbeam = { workspace = true }
|
||||
ctrlc = { version = "3.4.4" }
|
||||
indicatif = { workspace = true }
|
||||
|
||||
3
crates/ty/docs/cli.md
generated
3
crates/ty/docs/cli.md
generated
@@ -84,7 +84,8 @@ over all configuration files.</p>
|
||||
<li><code>3.11</code></li>
|
||||
<li><code>3.12</code></li>
|
||||
<li><code>3.13</code></li>
|
||||
</ul></dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-gitignore</code> to disable</p>
|
||||
</ul></dd><dt id="ty-check--quiet"><a href="#ty-check--quiet"><code>--quiet</code></a></dt><dd><p>Use quiet output</p>
|
||||
</dd><dt id="ty-check--respect-ignore-files"><a href="#ty-check--respect-ignore-files"><code>--respect-ignore-files</code></a></dt><dd><p>Respect file exclusions via <code>.gitignore</code> and other standard ignore files. Use <code>--no-respect-gitignore</code> to disable</p>
|
||||
</dd><dt id="ty-check--typeshed"><a href="#ty-check--typeshed"><code>--typeshed</code></a>, <code>--custom-typeshed-dir</code> <i>path</i></dt><dd><p>Custom directory to use for stdlib typeshed stubs</p>
|
||||
</dd><dt id="ty-check--verbose"><a href="#ty-check--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output (or <code>-vv</code> and <code>-vvv</code> for more verbose output)</p>
|
||||
</dd><dt id="ty-check--warn"><a href="#ty-check--warn"><code>--warn</code></a> <i>rule</i></dt><dd><p>Treat the given rule as having severity 'warn'. Can be specified multiple times.</p>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
mod args;
|
||||
mod logging;
|
||||
mod printer;
|
||||
mod python_version;
|
||||
mod version;
|
||||
|
||||
pub use args::Cli;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
use std::io::{self, BufWriter, Write, stdout};
|
||||
use std::fmt::Write;
|
||||
use std::process::{ExitCode, Termination};
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -14,6 +15,7 @@ use std::sync::Mutex;
|
||||
|
||||
use crate::args::{CheckCommand, Command, TerminalColor};
|
||||
use crate::logging::setup_tracing;
|
||||
use crate::printer::Printer;
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
@@ -25,7 +27,7 @@ use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
use ty_project::metadata::options::ProjectOptionsOverrides;
|
||||
use ty_project::watch::ProjectWatcher;
|
||||
use ty_project::{Db, DummyReporter, Reporter, watch};
|
||||
use ty_project::{Db, watch};
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_server::run_server;
|
||||
|
||||
@@ -42,6 +44,8 @@ pub fn run() -> anyhow::Result<ExitStatus> {
|
||||
Command::Check(check_args) => run_check(check_args),
|
||||
Command::Version => version().map(|()| ExitStatus::Success),
|
||||
Command::GenerateShellCompletion { shell } => {
|
||||
use std::io::stdout;
|
||||
|
||||
shell.generate(&mut Cli::command(), &mut stdout());
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
@@ -49,7 +53,7 @@ pub fn run() -> anyhow::Result<ExitStatus> {
|
||||
}
|
||||
|
||||
pub(crate) fn version() -> Result<()> {
|
||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||
let mut stdout = Printer::default().stream_for_requested_summary().lock();
|
||||
let version_info = crate::version::version();
|
||||
writeln!(stdout, "ty {}", &version_info)?;
|
||||
Ok(())
|
||||
@@ -59,9 +63,10 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
set_colored_override(args.color);
|
||||
|
||||
let verbosity = args.verbosity.level();
|
||||
countme::enable(verbosity.is_trace());
|
||||
let _guard = setup_tracing(verbosity, args.color.unwrap_or_default())?;
|
||||
|
||||
let printer = Printer::default().with_verbosity(verbosity);
|
||||
|
||||
tracing::warn!(
|
||||
"ty is pre-release software and not ready for production use. \
|
||||
Expect to encounter bugs, missing features, and fatal errors.",
|
||||
@@ -126,7 +131,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
}
|
||||
|
||||
let project_options_overrides = ProjectOptionsOverrides::new(config_file, options);
|
||||
let (main_loop, main_loop_cancellation_token) = MainLoop::new(project_options_overrides);
|
||||
let (main_loop, main_loop_cancellation_token) =
|
||||
MainLoop::new(project_options_overrides, printer);
|
||||
|
||||
// Listen to Ctrl+C and abort the watch mode.
|
||||
let main_loop_cancellation_token = Mutex::new(Some(main_loop_cancellation_token));
|
||||
@@ -144,7 +150,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
main_loop.run(&mut db)?
|
||||
};
|
||||
|
||||
let mut stdout = stdout().lock();
|
||||
let mut stdout = printer.stream_for_requested_summary().lock();
|
||||
match std::env::var(EnvVars::TY_MEMORY_REPORT).as_deref() {
|
||||
Ok("short") => write!(stdout, "{}", db.salsa_memory_dump().display_short())?,
|
||||
Ok("mypy_primer") => write!(stdout, "{}", db.salsa_memory_dump().display_mypy_primer())?,
|
||||
@@ -152,8 +158,6 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
tracing::trace!("Counts for entire CLI run:\n{}", countme::get_all());
|
||||
|
||||
std::mem::forget(db);
|
||||
|
||||
if exit_zero {
|
||||
@@ -195,12 +199,16 @@ struct MainLoop {
|
||||
/// The file system watcher, if running in watch mode.
|
||||
watcher: Option<ProjectWatcher>,
|
||||
|
||||
/// Interface for displaying information to the user.
|
||||
printer: Printer,
|
||||
|
||||
project_options_overrides: ProjectOptionsOverrides,
|
||||
}
|
||||
|
||||
impl MainLoop {
|
||||
fn new(
|
||||
project_options_overrides: ProjectOptionsOverrides,
|
||||
printer: Printer,
|
||||
) -> (Self, MainLoopCancellationToken) {
|
||||
let (sender, receiver) = crossbeam_channel::bounded(10);
|
||||
|
||||
@@ -210,6 +218,7 @@ impl MainLoop {
|
||||
receiver,
|
||||
watcher: None,
|
||||
project_options_overrides,
|
||||
printer,
|
||||
},
|
||||
MainLoopCancellationToken { sender },
|
||||
)
|
||||
@@ -226,32 +235,24 @@ impl MainLoop {
|
||||
|
||||
// Do not show progress bars with `--watch`, indicatif does not seem to
|
||||
// handle cancelling independent progress bars very well.
|
||||
self.run_with_progress::<DummyReporter>(db)?;
|
||||
// TODO(zanieb): We can probably use `MultiProgress` to handle this case in the future.
|
||||
self.printer = self.printer.with_no_progress();
|
||||
self.run(db)?;
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
fn run(self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
|
||||
self.run_with_progress::<IndicatifReporter>(db)
|
||||
}
|
||||
|
||||
fn run_with_progress<R>(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
|
||||
where
|
||||
R: Reporter + Default + 'static,
|
||||
{
|
||||
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
|
||||
|
||||
let result = self.main_loop::<R>(db);
|
||||
let result = self.main_loop(db);
|
||||
|
||||
tracing::debug!("Exiting main loop");
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn main_loop<R>(&mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
|
||||
where
|
||||
R: Reporter + Default + 'static,
|
||||
{
|
||||
fn main_loop(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
|
||||
// Schedule the first check.
|
||||
tracing::debug!("Starting main loop");
|
||||
|
||||
@@ -267,7 +268,7 @@ impl MainLoop {
|
||||
// to prevent blocking the main loop here.
|
||||
rayon::spawn(move || {
|
||||
match salsa::Cancelled::catch(|| {
|
||||
let mut reporter = R::default();
|
||||
let mut reporter = IndicatifReporter::from(self.printer);
|
||||
db.check_with_reporter(&mut reporter)
|
||||
}) {
|
||||
Ok(result) => {
|
||||
@@ -302,10 +303,12 @@ impl MainLoop {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let mut stdout = stdout().lock();
|
||||
|
||||
if result.is_empty() {
|
||||
writeln!(stdout, "{}", "All checks passed!".green().bold())?;
|
||||
writeln!(
|
||||
self.printer.stream_for_success_summary(),
|
||||
"{}",
|
||||
"All checks passed!".green().bold()
|
||||
)?;
|
||||
|
||||
if self.watcher.is_none() {
|
||||
return Ok(ExitStatus::Success);
|
||||
@@ -314,14 +317,19 @@ impl MainLoop {
|
||||
let mut max_severity = Severity::Info;
|
||||
let diagnostics_count = result.len();
|
||||
|
||||
let mut stdout = self.printer.stream_for_details().lock();
|
||||
for diagnostic in result {
|
||||
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
|
||||
// Only render diagnostics if they're going to be displayed, since doing
|
||||
// so is expensive.
|
||||
if stdout.is_enabled() {
|
||||
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
|
||||
}
|
||||
|
||||
max_severity = max_severity.max(diagnostic.severity());
|
||||
}
|
||||
|
||||
writeln!(
|
||||
stdout,
|
||||
self.printer.stream_for_failure_summary(),
|
||||
"Found {} diagnostic{}",
|
||||
diagnostics_count,
|
||||
if diagnostics_count > 1 { "s" } else { "" }
|
||||
@@ -353,8 +361,6 @@ impl MainLoop {
|
||||
"Discarding check result for outdated revision: current: {revision}, result revision: {check_revision}"
|
||||
);
|
||||
}
|
||||
|
||||
tracing::trace!("Counts after last check:\n{}", countme::get_all());
|
||||
}
|
||||
|
||||
MainLoopMessage::ApplyChanges(changes) => {
|
||||
@@ -383,27 +389,53 @@ impl MainLoop {
|
||||
}
|
||||
|
||||
/// A progress reporter for `ty check`.
|
||||
#[derive(Default)]
|
||||
struct IndicatifReporter(Option<indicatif::ProgressBar>);
|
||||
enum IndicatifReporter {
|
||||
/// A constructed reporter that is not yet ready, contains the target for the progress bar.
|
||||
Pending(indicatif::ProgressDrawTarget),
|
||||
/// A reporter that is ready, containing a progress bar to report to.
|
||||
///
|
||||
/// Initialization of the bar is deferred to [`ty_project::ProgressReporter::set_files`] so we
|
||||
/// do not initialize the bar too early as it may take a while to collect the number of files to
|
||||
/// process and we don't want to display an empty "0/0" bar.
|
||||
Initialized(indicatif::ProgressBar),
|
||||
}
|
||||
|
||||
impl ty_project::Reporter for IndicatifReporter {
|
||||
impl From<Printer> for IndicatifReporter {
|
||||
fn from(printer: Printer) -> Self {
|
||||
Self::Pending(printer.progress_target())
|
||||
}
|
||||
}
|
||||
|
||||
impl ty_project::ProgressReporter for IndicatifReporter {
|
||||
fn set_files(&mut self, files: usize) {
|
||||
let progress = indicatif::ProgressBar::new(files as u64);
|
||||
progress.set_style(
|
||||
let target = match std::mem::replace(
|
||||
self,
|
||||
IndicatifReporter::Pending(indicatif::ProgressDrawTarget::hidden()),
|
||||
) {
|
||||
Self::Pending(target) => target,
|
||||
Self::Initialized(_) => panic!("The progress reporter should only be initialized once"),
|
||||
};
|
||||
|
||||
let bar = indicatif::ProgressBar::with_draw_target(Some(files as u64), target);
|
||||
bar.set_style(
|
||||
indicatif::ProgressStyle::with_template(
|
||||
"{msg:8.dim} {bar:60.green/dim} {pos}/{len} files",
|
||||
)
|
||||
.unwrap()
|
||||
.progress_chars("--"),
|
||||
);
|
||||
progress.set_message("Checking");
|
||||
|
||||
self.0 = Some(progress);
|
||||
bar.set_message("Checking");
|
||||
*self = Self::Initialized(bar);
|
||||
}
|
||||
|
||||
fn report_file(&self, _file: &ruff_db::files::File) {
|
||||
if let Some(ref progress_bar) = self.0 {
|
||||
progress_bar.inc(1);
|
||||
match self {
|
||||
IndicatifReporter::Initialized(progress_bar) => {
|
||||
progress_bar.inc(1);
|
||||
}
|
||||
IndicatifReporter::Pending(_) => {
|
||||
panic!("`report_file` called before `set_files`")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,32 @@ pub(crate) struct Verbosity {
|
||||
help = "Use verbose output (or `-vv` and `-vvv` for more verbose output)",
|
||||
action = clap::ArgAction::Count,
|
||||
global = true,
|
||||
overrides_with = "quiet",
|
||||
)]
|
||||
verbose: u8,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use quiet output",
|
||||
action = clap::ArgAction::Count,
|
||||
global = true,
|
||||
overrides_with = "verbose",
|
||||
)]
|
||||
quiet: u8,
|
||||
}
|
||||
|
||||
impl Verbosity {
|
||||
/// Returns the verbosity level based on the number of `-v` flags.
|
||||
/// Returns the verbosity level based on the number of `-v` and `-q` flags.
|
||||
///
|
||||
/// Returns `None` if the user did not specify any verbosity flags.
|
||||
pub(crate) fn level(&self) -> VerbosityLevel {
|
||||
// `--quiet` and `--verbose` are mutually exclusive in Clap, so we can just check one first.
|
||||
match self.quiet {
|
||||
0 => {}
|
||||
_ => return VerbosityLevel::Quiet,
|
||||
// TODO(zanieb): Add support for `-qq` with a "silent" mode
|
||||
}
|
||||
|
||||
match self.verbose {
|
||||
0 => VerbosityLevel::Default,
|
||||
1 => VerbosityLevel::Verbose,
|
||||
@@ -42,9 +59,14 @@ impl Verbosity {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
pub(crate) enum VerbosityLevel {
|
||||
/// Quiet output. Only shows Ruff and ty events up to the [`ERROR`](tracing::Level::ERROR).
|
||||
/// Silences output except for summary information.
|
||||
Quiet,
|
||||
|
||||
/// Default output level. Only shows Ruff and ty events up to the [`WARN`](tracing::Level::WARN).
|
||||
#[default]
|
||||
Default,
|
||||
|
||||
/// Enables verbose output. Emits Ruff and ty events up to the [`INFO`](tracing::Level::INFO).
|
||||
@@ -62,6 +84,7 @@ pub(crate) enum VerbosityLevel {
|
||||
impl VerbosityLevel {
|
||||
const fn level_filter(self) -> LevelFilter {
|
||||
match self {
|
||||
VerbosityLevel::Quiet => LevelFilter::ERROR,
|
||||
VerbosityLevel::Default => LevelFilter::WARN,
|
||||
VerbosityLevel::Verbose => LevelFilter::INFO,
|
||||
VerbosityLevel::ExtraVerbose => LevelFilter::DEBUG,
|
||||
|
||||
172
crates/ty/src/printer.rs
Normal file
172
crates/ty/src/printer.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use std::io::StdoutLock;
|
||||
|
||||
use indicatif::ProgressDrawTarget;
|
||||
|
||||
use crate::logging::VerbosityLevel;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub(crate) struct Printer {
|
||||
verbosity: VerbosityLevel,
|
||||
no_progress: bool,
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
#[must_use]
|
||||
pub(crate) fn with_no_progress(self) -> Self {
|
||||
Self {
|
||||
verbosity: self.verbosity,
|
||||
no_progress: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_verbosity(self, verbosity: VerbosityLevel) -> Self {
|
||||
Self {
|
||||
verbosity,
|
||||
no_progress: self.no_progress,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`ProgressDrawTarget`] for this printer.
|
||||
pub(crate) fn progress_target(self) -> ProgressDrawTarget {
|
||||
if self.no_progress {
|
||||
return ProgressDrawTarget::hidden();
|
||||
}
|
||||
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Quiet => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::Default => ProgressDrawTarget::stderr(),
|
||||
// Hide the progress bar when in verbose mode.
|
||||
// Otherwise, it gets interleaved with log messages.
|
||||
VerbosityLevel::Verbose => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::ExtraVerbose => ProgressDrawTarget::hidden(),
|
||||
VerbosityLevel::Trace => ProgressDrawTarget::hidden(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for important messages.
|
||||
///
|
||||
/// Unlike [`Self::stdout_general`], the returned stream will be enabled when
|
||||
/// [`VerbosityLevel::Quiet`] is used.
|
||||
fn stdout_important(self) -> Stdout {
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Quiet => Stdout::enabled(),
|
||||
VerbosityLevel::Default => Stdout::enabled(),
|
||||
VerbosityLevel::Verbose => Stdout::enabled(),
|
||||
VerbosityLevel::ExtraVerbose => Stdout::enabled(),
|
||||
VerbosityLevel::Trace => Stdout::enabled(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for general messages.
|
||||
///
|
||||
/// The returned stream will be disabled when [`VerbosityLevel::Quiet`] is used.
|
||||
fn stdout_general(self) -> Stdout {
|
||||
match self.verbosity {
|
||||
VerbosityLevel::Quiet => Stdout::disabled(),
|
||||
VerbosityLevel::Default => Stdout::enabled(),
|
||||
VerbosityLevel::Verbose => Stdout::enabled(),
|
||||
VerbosityLevel::ExtraVerbose => Stdout::enabled(),
|
||||
VerbosityLevel::Trace => Stdout::enabled(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for a summary message that was explicitly requested by the
|
||||
/// user.
|
||||
///
|
||||
/// For example, in `ty version` the user has requested the version information and we should
|
||||
/// display it even if [`VerbosityLevel::Quiet`] is used. Or, in `ty check`, if the
|
||||
/// `TY_MEMORY_REPORT` variable has been set, we should display the memory report because the
|
||||
/// user has opted-in to display.
|
||||
pub(crate) fn stream_for_requested_summary(self) -> Stdout {
|
||||
self.stdout_important()
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for a summary message on failure.
|
||||
///
|
||||
/// For example, in `ty check`, this would be used for the message indicating the number of
|
||||
/// diagnostics found. The failure summary should capture information that is not reflected in
|
||||
/// the exit code.
|
||||
pub(crate) fn stream_for_failure_summary(self) -> Stdout {
|
||||
self.stdout_important()
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for a summary message on success.
|
||||
///
|
||||
/// For example, in `ty check`, this would be used for the message indicating that no diagnostic
|
||||
/// were found. The success summary does not capture important information for users that have
|
||||
/// opted-in to [`VerbosityLevel::Quiet`].
|
||||
pub(crate) fn stream_for_success_summary(self) -> Stdout {
|
||||
self.stdout_general()
|
||||
}
|
||||
|
||||
/// Return the [`Stdout`] stream for detailed messages.
|
||||
///
|
||||
/// For example, in `ty check`, this would be used for the diagnostic output.
|
||||
pub(crate) fn stream_for_details(self) -> Stdout {
|
||||
self.stdout_general()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum StreamStatus {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Stdout {
|
||||
status: StreamStatus,
|
||||
lock: Option<StdoutLock<'static>>,
|
||||
}
|
||||
|
||||
impl Stdout {
|
||||
fn enabled() -> Self {
|
||||
Self {
|
||||
status: StreamStatus::Enabled,
|
||||
lock: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled() -> Self {
|
||||
Self {
|
||||
status: StreamStatus::Disabled,
|
||||
lock: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lock(mut self) -> Self {
|
||||
match self.status {
|
||||
StreamStatus::Enabled => {
|
||||
// Drop the previous lock first, to avoid deadlocking
|
||||
self.lock.take();
|
||||
self.lock = Some(std::io::stdout().lock());
|
||||
}
|
||||
StreamStatus::Disabled => self.lock = None,
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn handle(&mut self) -> Box<dyn std::io::Write + '_> {
|
||||
match self.lock.as_mut() {
|
||||
Some(lock) => Box::new(lock),
|
||||
None => Box::new(std::io::stdout()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_enabled(&self) -> bool {
|
||||
matches!(self.status, StreamStatus::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Write for Stdout {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
match self.status {
|
||||
StreamStatus::Enabled => {
|
||||
let _ = write!(self.handle(), "{s}");
|
||||
Ok(())
|
||||
}
|
||||
StreamStatus::Disabled => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,64 @@ use std::{
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_quiet_output() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_file("test.py", "x: int = 1")?;
|
||||
|
||||
// By default, we emit an "all checks passed" message
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
");
|
||||
|
||||
// With `quiet`, the message is not displayed
|
||||
assert_cmd_snapshot!(case.command().arg("--quiet"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let case = CliTest::with_file("test.py", "x: int = 'foo'")?;
|
||||
|
||||
// By default, we emit a diagnostic
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-assignment]: Object of type `Literal["foo"]` is not assignable to `int`
|
||||
--> test.py:1:1
|
||||
|
|
||||
1 | x: int = 'foo'
|
||||
| ^
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"#);
|
||||
|
||||
// With `quiet`, the diagnostic is not displayed, just the summary message
|
||||
assert_cmd_snapshot!(case.command().arg("--quiet"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_in_sub_directory() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([("test.py", "~"), ("subdir/nothing", "")])?;
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{cmp, fmt};
|
||||
|
||||
use crate::metadata::settings::file_settings;
|
||||
use crate::{DEFAULT_LINT_REGISTRY, DummyReporter};
|
||||
use crate::{Project, ProjectMetadata, Reporter};
|
||||
use crate::{ProgressReporter, Project, ProjectMetadata};
|
||||
use ruff_db::Db as SourceDb;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{File, Files};
|
||||
@@ -87,7 +87,7 @@ impl ProjectDatabase {
|
||||
}
|
||||
|
||||
/// Checks all open files in the project and its dependencies, using the given reporter.
|
||||
pub fn check_with_reporter(&self, reporter: &mut dyn Reporter) -> Vec<Diagnostic> {
|
||||
pub fn check_with_reporter(&self, reporter: &mut dyn ProgressReporter) -> Vec<Diagnostic> {
|
||||
let reporter = AssertUnwindSafe(reporter);
|
||||
self.project().check(self, CheckMode::OpenFiles, reporter)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ impl ProjectDatabase {
|
||||
/// Check the project with the given mode.
|
||||
pub fn check_with_mode(&self, mode: CheckMode) -> Vec<Diagnostic> {
|
||||
let mut reporter = DummyReporter;
|
||||
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter);
|
||||
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn ProgressReporter);
|
||||
self.project().check(self, mode, reporter)
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ pub struct Project {
|
||||
}
|
||||
|
||||
/// A progress reporter.
|
||||
pub trait Reporter: Send + Sync {
|
||||
pub trait ProgressReporter: Send + Sync {
|
||||
/// Initialize the reporter with the number of files.
|
||||
fn set_files(&mut self, files: usize);
|
||||
|
||||
@@ -121,11 +121,11 @@ pub trait Reporter: Send + Sync {
|
||||
fn report_file(&self, file: &File);
|
||||
}
|
||||
|
||||
/// A no-op implementation of [`Reporter`].
|
||||
/// A no-op implementation of [`ProgressReporter`].
|
||||
#[derive(Default)]
|
||||
pub struct DummyReporter;
|
||||
|
||||
impl Reporter for DummyReporter {
|
||||
impl ProgressReporter for DummyReporter {
|
||||
fn set_files(&mut self, _files: usize) {}
|
||||
fn report_file(&self, _file: &File) {}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ impl Project {
|
||||
self,
|
||||
db: &ProjectDatabase,
|
||||
mode: CheckMode,
|
||||
mut reporter: AssertUnwindSafe<&mut dyn Reporter>,
|
||||
mut reporter: AssertUnwindSafe<&mut dyn ProgressReporter>,
|
||||
) -> Vec<Diagnostic> {
|
||||
let project_span = tracing::debug_span!("Project::check");
|
||||
let _span = project_span.enter();
|
||||
@@ -257,8 +257,11 @@ impl Project {
|
||||
tracing::debug_span!(parent: project_span, "check_file", ?file);
|
||||
let _entered = check_file_span.entered();
|
||||
|
||||
let result = self.check_file_impl(&db, file);
|
||||
file_diagnostics.lock().unwrap().extend(result);
|
||||
let result = check_file_impl(&db, file);
|
||||
file_diagnostics
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend(result.iter().map(Clone::clone));
|
||||
|
||||
reporter.report_file(&file);
|
||||
});
|
||||
@@ -285,7 +288,7 @@ impl Project {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
self.check_file_impl(db, file)
|
||||
check_file_impl(db, file).iter().map(Clone::clone).collect()
|
||||
}
|
||||
|
||||
/// Opens a file in the project.
|
||||
@@ -466,71 +469,73 @@ impl Project {
|
||||
self.set_file_set(db).to(IndexedFiles::lazy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_file_impl(self, db: &dyn Db, file: File) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
#[salsa::tracked(returns(deref), heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Box<[Diagnostic]> {
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
// Abort checking if there are IO errors.
|
||||
let source = source_text(db, file);
|
||||
// Abort checking if there are IO errors.
|
||||
let source = source_text(db, file);
|
||||
|
||||
if let Some(read_error) = source.read_error() {
|
||||
diagnostics.push(
|
||||
IOErrorDiagnostic {
|
||||
file: Some(file),
|
||||
error: read_error.clone().into(),
|
||||
}
|
||||
.to_diagnostic(),
|
||||
);
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
let parsed = parsed_module(db, file);
|
||||
|
||||
let parsed_ref = parsed.load(db);
|
||||
diagnostics.extend(
|
||||
parsed_ref
|
||||
.errors()
|
||||
.iter()
|
||||
.map(|error| Diagnostic::invalid_syntax(file, &error.error, error)),
|
||||
);
|
||||
|
||||
diagnostics.extend(parsed_ref.unsupported_syntax_errors().iter().map(|error| {
|
||||
let mut error = Diagnostic::invalid_syntax(file, error, error);
|
||||
add_inferred_python_version_hint_to_diagnostic(db, &mut error, "parsing syntax");
|
||||
error
|
||||
}));
|
||||
|
||||
{
|
||||
let db = AssertUnwindSafe(db);
|
||||
match catch(&**db, file, || check_types(*db, file)) {
|
||||
Ok(Some(type_check_diagnostics)) => {
|
||||
diagnostics.extend(type_check_diagnostics.into_iter().cloned());
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(diagnostic) => diagnostics.push(diagnostic),
|
||||
if let Some(read_error) = source.read_error() {
|
||||
diagnostics.push(
|
||||
IOErrorDiagnostic {
|
||||
file: Some(file),
|
||||
error: read_error.clone().into(),
|
||||
}
|
||||
}
|
||||
|
||||
if self
|
||||
.open_fileset(db)
|
||||
.is_none_or(|files| !files.contains(&file))
|
||||
{
|
||||
// Drop the AST now that we are done checking this file. It is not currently open,
|
||||
// so it is unlikely to be accessed again soon. If any queries need to access the AST
|
||||
// from across files, it will be re-parsed.
|
||||
parsed.clear();
|
||||
}
|
||||
|
||||
diagnostics.sort_unstable_by_key(|diagnostic| {
|
||||
diagnostic
|
||||
.primary_span()
|
||||
.and_then(|span| span.range())
|
||||
.unwrap_or_default()
|
||||
.start()
|
||||
});
|
||||
|
||||
diagnostics
|
||||
.to_diagnostic(),
|
||||
);
|
||||
return diagnostics.into_boxed_slice();
|
||||
}
|
||||
|
||||
let parsed = parsed_module(db, file);
|
||||
|
||||
let parsed_ref = parsed.load(db);
|
||||
diagnostics.extend(
|
||||
parsed_ref
|
||||
.errors()
|
||||
.iter()
|
||||
.map(|error| Diagnostic::invalid_syntax(file, &error.error, error)),
|
||||
);
|
||||
|
||||
diagnostics.extend(parsed_ref.unsupported_syntax_errors().iter().map(|error| {
|
||||
let mut error = Diagnostic::invalid_syntax(file, error, error);
|
||||
add_inferred_python_version_hint_to_diagnostic(db, &mut error, "parsing syntax");
|
||||
error
|
||||
}));
|
||||
|
||||
{
|
||||
let db = AssertUnwindSafe(db);
|
||||
match catch(&**db, file, || check_types(*db, file)) {
|
||||
Ok(Some(type_check_diagnostics)) => {
|
||||
diagnostics.extend(type_check_diagnostics);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(diagnostic) => diagnostics.push(diagnostic),
|
||||
}
|
||||
}
|
||||
|
||||
if db
|
||||
.project()
|
||||
.open_fileset(db)
|
||||
.is_none_or(|files| !files.contains(&file))
|
||||
{
|
||||
// Drop the AST now that we are done checking this file. It is not currently open,
|
||||
// so it is unlikely to be accessed again soon. If any queries need to access the AST
|
||||
// from across files, it will be re-parsed.
|
||||
parsed.clear();
|
||||
}
|
||||
|
||||
diagnostics.sort_unstable_by_key(|diagnostic| {
|
||||
diagnostic
|
||||
.primary_span()
|
||||
.and_then(|span| span.range())
|
||||
.unwrap_or_default()
|
||||
.start()
|
||||
});
|
||||
|
||||
diagnostics.into_boxed_slice()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -701,8 +706,8 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Db;
|
||||
use crate::ProjectMetadata;
|
||||
use crate::check_file_impl;
|
||||
use crate::db::tests::TestDb;
|
||||
use ruff_db::Db as _;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
@@ -741,9 +746,8 @@ mod tests {
|
||||
|
||||
assert_eq!(source_text(&db, file).as_str(), "");
|
||||
assert_eq!(
|
||||
db.project()
|
||||
.check_file_impl(&db, file)
|
||||
.into_iter()
|
||||
check_file_impl(&db, file)
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.primary_message().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["Failed to read file: No such file or directory".to_string()]
|
||||
@@ -758,9 +762,8 @@ mod tests {
|
||||
|
||||
assert_eq!(source_text(&db, file).as_str(), "");
|
||||
assert_eq!(
|
||||
db.project()
|
||||
.check_file_impl(&db, file)
|
||||
.into_iter()
|
||||
check_file_impl(&db, file)
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.primary_message().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![] as Vec<String>
|
||||
|
||||
@@ -29,7 +29,6 @@ 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 }
|
||||
|
||||
@@ -1304,7 +1304,7 @@ scope of the name that was declared `global`, can add a symbol to the global nam
|
||||
def f():
|
||||
global g, h
|
||||
|
||||
g: bool = True
|
||||
g = True
|
||||
|
||||
f()
|
||||
```
|
||||
|
||||
@@ -83,7 +83,7 @@ def f():
|
||||
x = 1
|
||||
def g() -> None:
|
||||
nonlocal x
|
||||
global x # TODO: error: [invalid-syntax] "name 'x' is nonlocal and global"
|
||||
global x # error: [invalid-syntax] "name `x` is nonlocal and global"
|
||||
x = None
|
||||
```
|
||||
|
||||
@@ -209,5 +209,18 @@ x: int = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
x: str = "foo" # TODO: error: [invalid-syntax] "annotated name 'x' can't be global"
|
||||
x: str = "foo" # error: [invalid-syntax] "annotated name `x` can't be global"
|
||||
```
|
||||
|
||||
## Global declarations affect the inferred type of the binding
|
||||
|
||||
Even if the `global` declaration isn't used in an assignment, we conservatively assume it could be:
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
|
||||
# TODO: reveal_type(x) # revealed: Unknown | Literal["1"]
|
||||
```
|
||||
|
||||
@@ -43,3 +43,321 @@ def f():
|
||||
def h():
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
## The `nonlocal` keyword
|
||||
|
||||
Without the `nonlocal` keyword, bindings in an inner scope shadow variables of the same name in
|
||||
enclosing scopes. This example isn't a type error, because the inner `x` shadows the outer one:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x: int = 1
|
||||
def g():
|
||||
x = "hello" # allowed
|
||||
```
|
||||
|
||||
With `nonlocal` it is a type error, because `x` refers to the same place in both scopes:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x: int = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
x = "hello" # error: [invalid-assignment] "Object of type `Literal["hello"]` is not assignable to `int`"
|
||||
```
|
||||
|
||||
## Local variable bindings "look ahead" to any assignment in the current scope
|
||||
|
||||
The binding `x = 2` in `g` causes the earlier read of `x` to refer to `g`'s not-yet-initialized
|
||||
binding, rather than to `x = 1` in `f`'s scope:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
if x == 1: # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
x = 2
|
||||
```
|
||||
|
||||
The `nonlocal` keyword makes this example legal (and makes the assignment `x = 2` affect the outer
|
||||
scope):
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
if x == 1:
|
||||
x = 2
|
||||
```
|
||||
|
||||
For the same reason, using the `+=` operator in an inner scope is an error without `nonlocal`
|
||||
(unless you shadow the outer variable first):
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
x += 1 # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
x = 1
|
||||
x += 1 # allowed, but doesn't affect the outer scope
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
x += 1 # allowed, and affects the outer scope
|
||||
```
|
||||
|
||||
## `nonlocal` declarations must match an outer binding
|
||||
|
||||
`nonlocal x` isn't allowed when there's no binding for `x` in an enclosing scope:
|
||||
|
||||
```py
|
||||
def f():
|
||||
def g():
|
||||
nonlocal x # error: [invalid-syntax] "no binding for nonlocal `x` found"
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x, y # error: [invalid-syntax] "no binding for nonlocal `y` found"
|
||||
```
|
||||
|
||||
A global `x` doesn't work. The target must be in a function-like scope:
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
def g():
|
||||
nonlocal x # error: [invalid-syntax] "no binding for nonlocal `x` found"
|
||||
|
||||
def f():
|
||||
global x
|
||||
def g():
|
||||
nonlocal x # error: [invalid-syntax] "no binding for nonlocal `x` found"
|
||||
```
|
||||
|
||||
A class-scoped `x` also doesn't work:
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
x = 1
|
||||
@staticmethod
|
||||
def f():
|
||||
nonlocal x # error: [invalid-syntax] "no binding for nonlocal `x` found"
|
||||
```
|
||||
|
||||
However, class-scoped bindings don't break the `nonlocal` chain the way `global` declarations do:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x: int = 1
|
||||
|
||||
class Foo:
|
||||
x: str = "hello"
|
||||
|
||||
@staticmethod
|
||||
def g():
|
||||
# Skips the class scope and reaches the outer function scope.
|
||||
nonlocal x
|
||||
x = 2 # allowed
|
||||
x = "goodbye" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## `nonlocal` uses the closest binding
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
x = 2
|
||||
def h():
|
||||
nonlocal x
|
||||
reveal_type(x) # revealed: Unknown | Literal[2]
|
||||
```
|
||||
|
||||
## `nonlocal` "chaining"
|
||||
|
||||
Multiple `nonlocal` statements can "chain" through nested scopes:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
def h():
|
||||
nonlocal x
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
And the `nonlocal` chain can skip over a scope that doesn't bind the variable:
|
||||
|
||||
```py
|
||||
def f1():
|
||||
x = 1
|
||||
def f2():
|
||||
nonlocal x
|
||||
def f3():
|
||||
# No binding; this scope gets skipped.
|
||||
def f4():
|
||||
nonlocal x
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
But a `global` statement breaks the chain:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
global x
|
||||
def h():
|
||||
nonlocal x # error: [invalid-syntax] "no binding for nonlocal `x` found"
|
||||
```
|
||||
|
||||
## `nonlocal` bindings respect declared types from the defining scope, even without a binding
|
||||
|
||||
```py
|
||||
def f():
|
||||
x: int
|
||||
def g():
|
||||
nonlocal x
|
||||
x = "string" # error: [invalid-assignment] "Object of type `Literal["string"]` is not assignable to `int`"
|
||||
```
|
||||
|
||||
## A complicated mixture of `nonlocal` chaining, empty scopes, class scopes, and the `global` keyword
|
||||
|
||||
```py
|
||||
def f1():
|
||||
# The original bindings of `x`, `y`, and `z` with type declarations.
|
||||
x: int = 1
|
||||
y: int = 2
|
||||
z: int = 3
|
||||
|
||||
def f2():
|
||||
# This scope doesn't touch `x`, `y`, or `z` at all.
|
||||
|
||||
class Foo:
|
||||
# This class scope is totally ignored.
|
||||
x: str = "a"
|
||||
y: str = "b"
|
||||
z: str = "c"
|
||||
|
||||
@staticmethod
|
||||
def f3():
|
||||
# This scope declares `x` nonlocal and `y` as global, and it shadows `z` without
|
||||
# giving it a type declaration.
|
||||
nonlocal x
|
||||
x = 4
|
||||
y = 5
|
||||
global z
|
||||
z = 6
|
||||
|
||||
def f4():
|
||||
# This scope sees `x` from `f1` and `y` from `f3`. It *can't* declare `z`
|
||||
# nonlocal, because of the global statement above, but it *can* load `z` as a
|
||||
# "free" variable, in which case it sees the global value.
|
||||
nonlocal x, y, z # error: [invalid-syntax] "no binding for nonlocal `z` found"
|
||||
x = "string" # error: [invalid-assignment]
|
||||
y = "string" # allowed, because `f3`'s `y` is untyped
|
||||
reveal_type(z) # revealed: Unknown | Literal[6]
|
||||
```
|
||||
|
||||
## TODO: `nonlocal` affects the inferred type in the outer scope
|
||||
|
||||
Without `nonlocal`, `g` can't write to `x`, and the inferred type of `x` in `f`'s scope isn't
|
||||
affected by `g`:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
But with `nonlocal`, `g` could write to `x`, and that affects its inferred type in `f`. That's true
|
||||
regardless of whether `g` actually writes to `x`. With a write:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
x += 1
|
||||
reveal_type(x) # revealed: Unknown | Literal[2]
|
||||
# TODO: should be `Unknown | Literal[1]`
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
Without a write:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
# TODO: should be `Unknown | Literal[1]`
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Annotating a `nonlocal` binding is a syntax error
|
||||
|
||||
```py
|
||||
def f():
|
||||
x: int = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
x: str = "foo" # error: [invalid-syntax] "annotated name `x` can't be nonlocal"
|
||||
```
|
||||
|
||||
## Use before `nonlocal`
|
||||
|
||||
Using a name prior to its `nonlocal` declaration in the same scope is a syntax error:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
x = 2
|
||||
nonlocal x # error: [invalid-syntax] "name `x` is used prior to nonlocal declaration"
|
||||
```
|
||||
|
||||
This is true even if there are multiple `nonlocal` declarations of the same variable, as long as any
|
||||
of them come after the usage:
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
x = 2
|
||||
nonlocal x # error: [invalid-syntax] "name `x` is used prior to nonlocal declaration"
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
def g():
|
||||
nonlocal x
|
||||
nonlocal x
|
||||
x = 2 # allowed
|
||||
```
|
||||
|
||||
## `nonlocal` before outer initialization
|
||||
|
||||
`nonlocal x` works even if `x` isn't bound in the enclosing scope until afterwards:
|
||||
|
||||
```py
|
||||
def f():
|
||||
def g():
|
||||
# This is allowed, because of the subsequent definition of `x`.
|
||||
nonlocal x
|
||||
x = 1
|
||||
```
|
||||
|
||||
@@ -502,5 +502,55 @@ def f6(a, /): ...
|
||||
static_assert(not is_equivalent_to(CallableTypeOf[f1], CallableTypeOf[f6]))
|
||||
```
|
||||
|
||||
## Module-literal types
|
||||
|
||||
Two "copies" of a single-file module are considered equivalent types, even if the different copies
|
||||
were originally imported in different first-party modules:
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
import typing
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import typing
|
||||
from module import typing as other_typing
|
||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||
|
||||
static_assert(is_equivalent_to(TypeOf[typing], TypeOf[other_typing]))
|
||||
static_assert(is_equivalent_to(TypeOf[typing] | int | str, str | int | TypeOf[other_typing]))
|
||||
```
|
||||
|
||||
We currently do not consider module-literal types to be equivalent if the underlying module is a
|
||||
package and the different "copies" of the module were originally imported in different modules. This
|
||||
is because we might consider submodules to be available as attributes on one copy but not on the
|
||||
other, depending on whether those submodules were explicitly imported in the original importing
|
||||
module:
|
||||
|
||||
`module2.py`:
|
||||
|
||||
```py
|
||||
import importlib
|
||||
import importlib.abc
|
||||
```
|
||||
|
||||
`main2.py`:
|
||||
|
||||
```py
|
||||
import importlib
|
||||
from module2 import importlib as other_importlib
|
||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||
|
||||
# error: [unresolved-attribute] "Type `<module 'importlib'>` has no attribute `abc`"
|
||||
reveal_type(importlib.abc) # revealed: Unknown
|
||||
|
||||
reveal_type(other_importlib.abc) # revealed: <module 'importlib.abc'>
|
||||
|
||||
static_assert(not is_equivalent_to(TypeOf[importlib], TypeOf[other_importlib]))
|
||||
```
|
||||
|
||||
[materializations]: https://typing.python.org/en/latest/spec/glossary.html#term-materialize
|
||||
[the equivalence relation]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent
|
||||
|
||||
@@ -147,8 +147,7 @@ def nonlocal_use():
|
||||
X: Final[int] = 1
|
||||
def inner():
|
||||
nonlocal X
|
||||
# TODO: this should be an error
|
||||
X = 2
|
||||
X = 2 # error: [invalid-assignment] "Reassignment of `Final` symbol `X` is not allowed: Reassignment of `Final` symbol"
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
@@ -1421,7 +1421,7 @@ impl RequiresExplicitReExport {
|
||||
/// ```py
|
||||
/// def _():
|
||||
/// x = 1
|
||||
///
|
||||
///
|
||||
/// x = 2
|
||||
///
|
||||
/// if flag():
|
||||
|
||||
@@ -217,9 +217,6 @@ pub(crate) struct SemanticIndex<'db> {
|
||||
/// Map from the file-local [`FileScopeId`] to the salsa-ingredient [`ScopeId`].
|
||||
scope_ids_by_scope: IndexVec<FileScopeId, ScopeId<'db>>,
|
||||
|
||||
/// Map from the file-local [`FileScopeId`] to the set of explicit-global symbols it contains.
|
||||
globals_by_scope: FxHashMap<FileScopeId, FxHashSet<ScopedPlaceId>>,
|
||||
|
||||
/// Use-def map for each scope in this file.
|
||||
use_def_maps: IndexVec<FileScopeId, ArcUseDefMap<'db>>,
|
||||
|
||||
@@ -308,9 +305,19 @@ impl<'db> SemanticIndex<'db> {
|
||||
symbol: ScopedPlaceId,
|
||||
scope: FileScopeId,
|
||||
) -> bool {
|
||||
self.globals_by_scope
|
||||
.get(&scope)
|
||||
.is_some_and(|globals| globals.contains(&symbol))
|
||||
self.place_table(scope)
|
||||
.place_expr(symbol)
|
||||
.is_marked_global()
|
||||
}
|
||||
|
||||
pub(crate) fn symbol_is_nonlocal_in_scope(
|
||||
&self,
|
||||
symbol: ScopedPlaceId,
|
||||
scope: FileScopeId,
|
||||
) -> bool {
|
||||
self.place_table(scope)
|
||||
.place_expr(symbol)
|
||||
.is_marked_nonlocal()
|
||||
}
|
||||
|
||||
/// Returns the id of the parent scope.
|
||||
|
||||
@@ -83,6 +83,8 @@ pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||
current_match_case: Option<CurrentMatchCase<'ast>>,
|
||||
/// The name of the first function parameter of the innermost function that we're currently visiting.
|
||||
current_first_parameter_name: Option<&'ast str>,
|
||||
/// Functions defined in the current scope. We walk their bodies at the end of the scope.
|
||||
deferred_function_bodies: Vec<&'ast ast::StmtFunctionDef>,
|
||||
|
||||
/// Per-scope contexts regarding nested `try`/`except` statements
|
||||
try_node_context_stack_manager: TryNodeContextStackManager,
|
||||
@@ -103,7 +105,6 @@ pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||
use_def_maps: IndexVec<FileScopeId, UseDefMapBuilder<'db>>,
|
||||
scopes_by_node: FxHashMap<NodeWithScopeKey, FileScopeId>,
|
||||
scopes_by_expression: FxHashMap<ExpressionNodeKey, FileScopeId>,
|
||||
globals_by_scope: FxHashMap<FileScopeId, FxHashSet<ScopedPlaceId>>,
|
||||
definitions_by_node: FxHashMap<DefinitionNodeKey, Definitions<'db>>,
|
||||
expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
|
||||
imported_modules: FxHashSet<ModuleName>,
|
||||
@@ -127,6 +128,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
current_assignments: vec![],
|
||||
current_match_case: None,
|
||||
current_first_parameter_name: None,
|
||||
deferred_function_bodies: Vec::new(),
|
||||
try_node_context_stack_manager: TryNodeContextStackManager::default(),
|
||||
|
||||
has_future_annotations: false,
|
||||
@@ -141,7 +143,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
scopes_by_node: FxHashMap::default(),
|
||||
definitions_by_node: FxHashMap::default(),
|
||||
expressions_by_node: FxHashMap::default(),
|
||||
globals_by_scope: FxHashMap::default(),
|
||||
|
||||
imported_modules: FxHashSet::default(),
|
||||
generator_functions: FxHashSet::default(),
|
||||
@@ -259,7 +260,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
.push(UseDefMapBuilder::new(is_class_scope));
|
||||
let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default());
|
||||
|
||||
let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default());
|
||||
let scope_id = ScopeId::new(self.db, self.file, file_scope_id);
|
||||
|
||||
self.scope_ids_by_scope.push(scope_id);
|
||||
let previous = self.scopes_by_node.insert(node.node_key(), file_scope_id);
|
||||
@@ -349,7 +350,12 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
popped_scope_id
|
||||
}
|
||||
|
||||
fn current_place_table(&mut self) -> &mut PlaceTableBuilder {
|
||||
fn current_place_table(&self) -> &PlaceTableBuilder {
|
||||
let scope_id = self.current_scope();
|
||||
&self.place_tables[scope_id]
|
||||
}
|
||||
|
||||
fn current_place_table_mut(&mut self) -> &mut PlaceTableBuilder {
|
||||
let scope_id = self.current_scope();
|
||||
&mut self.place_tables[scope_id]
|
||||
}
|
||||
@@ -389,7 +395,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
/// Add a symbol to the place table and the use-def map.
|
||||
/// Return the [`ScopedPlaceId`] that uniquely identifies the symbol in both.
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedPlaceId {
|
||||
let (place_id, added) = self.current_place_table().add_symbol(name);
|
||||
let (place_id, added) = self.current_place_table_mut().add_symbol(name);
|
||||
if added {
|
||||
self.current_use_def_map_mut().add_place(place_id);
|
||||
}
|
||||
@@ -399,7 +405,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
/// Add a place to the place table and the use-def map.
|
||||
/// Return the [`ScopedPlaceId`] that uniquely identifies the place in both.
|
||||
fn add_place(&mut self, place_expr: PlaceExprWithFlags) -> ScopedPlaceId {
|
||||
let (place_id, added) = self.current_place_table().add_place(place_expr);
|
||||
let (place_id, added) = self.current_place_table_mut().add_place(place_expr);
|
||||
if added {
|
||||
self.current_use_def_map_mut().add_place(place_id);
|
||||
}
|
||||
@@ -407,15 +413,15 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
fn mark_place_bound(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table().mark_place_bound(id);
|
||||
self.current_place_table_mut().mark_place_bound(id);
|
||||
}
|
||||
|
||||
fn mark_place_declared(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table().mark_place_declared(id);
|
||||
self.current_place_table_mut().mark_place_declared(id);
|
||||
}
|
||||
|
||||
fn mark_place_used(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table().mark_place_used(id);
|
||||
self.current_place_table_mut().mark_place_used(id);
|
||||
}
|
||||
|
||||
fn add_entry_for_definition_key(&mut self, key: DefinitionNodeKey) -> &mut Definitions<'db> {
|
||||
@@ -495,7 +501,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
place,
|
||||
kind,
|
||||
is_reexported,
|
||||
countme::Count::default(),
|
||||
);
|
||||
|
||||
let num_definitions = {
|
||||
@@ -731,7 +736,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
subject,
|
||||
kind,
|
||||
guard,
|
||||
countme::Count::default(),
|
||||
);
|
||||
let predicate = PredicateOrLiteral::Predicate(Predicate {
|
||||
node: PredicateNode::Pattern(pattern_predicate),
|
||||
@@ -781,7 +785,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
AstNodeRef::new(self.module, expression_node),
|
||||
assigned_to.map(|assigned_to| AstNodeRef::new(self.module, assigned_to)),
|
||||
expression_kind,
|
||||
countme::Count::default(),
|
||||
);
|
||||
self.expressions_by_node
|
||||
.insert(expression_node.into(), expression);
|
||||
@@ -986,7 +989,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
// Note `target` belongs to the `self.module` tree
|
||||
AstNodeRef::new(self.module, target),
|
||||
UnpackValue::new(unpackable.kind(), value),
|
||||
countme::Count::default(),
|
||||
));
|
||||
Some(unpackable.as_current_assignment(unpack))
|
||||
}
|
||||
@@ -1008,8 +1010,83 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_function_body(&mut self, function_def: &'ast ast::StmtFunctionDef) {
|
||||
let ast::StmtFunctionDef {
|
||||
parameters,
|
||||
type_params,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
} = function_def;
|
||||
self.with_type_params(
|
||||
NodeWithScopeRef::FunctionTypeParameters(function_def),
|
||||
type_params.as_deref(),
|
||||
|builder| {
|
||||
builder.visit_parameters(parameters);
|
||||
if let Some(returns) = returns {
|
||||
builder.visit_annotation(returns);
|
||||
}
|
||||
|
||||
builder.push_scope(NodeWithScopeRef::Function(function_def));
|
||||
|
||||
builder.declare_parameters(parameters);
|
||||
|
||||
let mut first_parameter_name = parameters
|
||||
.iter_non_variadic_params()
|
||||
.next()
|
||||
.map(|first_param| first_param.parameter.name.id().as_str());
|
||||
std::mem::swap(
|
||||
&mut builder.current_first_parameter_name,
|
||||
&mut first_parameter_name,
|
||||
);
|
||||
|
||||
builder.visit_scoped_body(body);
|
||||
|
||||
builder.current_first_parameter_name = first_parameter_name;
|
||||
builder.pop_scope()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Walk the body of a scope, either the global scope or a function scope.
|
||||
///
|
||||
/// When we encounter a (top-level or nested) function definition, we add the function's name
|
||||
/// to the current scope, but we defer walking its body until the end. (See the `FunctionDef`
|
||||
/// branch of `visit_stmt`.) This deferred approach is necessary to be able to check `nonlocal`
|
||||
/// statements as we encounter them, for example:
|
||||
///
|
||||
/// ```py
|
||||
/// def f():
|
||||
/// def g():
|
||||
/// nonlocal x # allowed
|
||||
/// nonlocal y # SyntaxError: no binding for nonlocal 'y' found
|
||||
/// x = 1
|
||||
/// ```
|
||||
///
|
||||
/// See the comments in the `Nonlocal` branch of `visit_stmt`, which relies on this binding
|
||||
/// information being present.
|
||||
fn visit_scoped_body(&mut self, body: &'ast [ast::Stmt]) {
|
||||
debug_assert!(
|
||||
self.deferred_function_bodies.is_empty(),
|
||||
"every function starts with a clean scope",
|
||||
);
|
||||
|
||||
// If this scope contains function definitions, they'll be added to
|
||||
// `self.deferred_function_bodies` as we walk each statement.
|
||||
self.visit_body(body);
|
||||
|
||||
// Now that we've walked all the statements in this scope, walk any deferred function
|
||||
// bodies. This is recursive, so we need to clear out the contents of
|
||||
// `self.deferred_function_bodies` and give each function a fresh list (or else we'll fail
|
||||
// the `debug_assert!` above).
|
||||
let taken_deferred_function_bodies = std::mem::take(&mut self.deferred_function_bodies);
|
||||
for function_def in taken_deferred_function_bodies {
|
||||
self.visit_function_body(function_def);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn build(mut self) -> SemanticIndex<'db> {
|
||||
self.visit_body(self.module.suite());
|
||||
self.visit_scoped_body(self.module.suite());
|
||||
|
||||
// Pop the root scope
|
||||
self.pop_scope();
|
||||
@@ -1046,7 +1123,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
self.scopes_by_node.shrink_to_fit();
|
||||
self.generator_functions.shrink_to_fit();
|
||||
self.eager_snapshots.shrink_to_fit();
|
||||
self.globals_by_scope.shrink_to_fit();
|
||||
|
||||
SemanticIndex {
|
||||
place_tables,
|
||||
@@ -1054,7 +1130,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
definitions_by_node: self.definitions_by_node,
|
||||
expressions_by_node: self.expressions_by_node,
|
||||
scope_ids_by_scope: self.scope_ids_by_scope,
|
||||
globals_by_scope: self.globals_by_scope,
|
||||
ast_ids,
|
||||
scopes_by_expression: self.scopes_by_expression,
|
||||
scopes_by_node: self.scopes_by_node,
|
||||
@@ -1088,46 +1163,19 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
let ast::StmtFunctionDef {
|
||||
decorator_list,
|
||||
parameters,
|
||||
type_params,
|
||||
name,
|
||||
returns,
|
||||
body,
|
||||
is_async: _,
|
||||
range: _,
|
||||
node_index: _,
|
||||
..
|
||||
} = function_def;
|
||||
|
||||
// Like Ruff, we don't walk the body of the function here. Instead, we defer it to
|
||||
// the end of the current scope. See `visit_scoped_body`. See also the comments in
|
||||
// the `Nonlocal` branch below about why this deferred visit order is necessary.
|
||||
self.deferred_function_bodies.push(function_def);
|
||||
|
||||
for decorator in decorator_list {
|
||||
self.visit_decorator(decorator);
|
||||
}
|
||||
|
||||
self.with_type_params(
|
||||
NodeWithScopeRef::FunctionTypeParameters(function_def),
|
||||
type_params.as_deref(),
|
||||
|builder| {
|
||||
builder.visit_parameters(parameters);
|
||||
if let Some(returns) = returns {
|
||||
builder.visit_annotation(returns);
|
||||
}
|
||||
|
||||
builder.push_scope(NodeWithScopeRef::Function(function_def));
|
||||
|
||||
builder.declare_parameters(parameters);
|
||||
|
||||
let mut first_parameter_name = parameters
|
||||
.iter_non_variadic_params()
|
||||
.next()
|
||||
.map(|first_param| first_param.parameter.name.id().as_str());
|
||||
std::mem::swap(
|
||||
&mut builder.current_first_parameter_name,
|
||||
&mut first_parameter_name,
|
||||
);
|
||||
|
||||
builder.visit_body(body);
|
||||
|
||||
builder.current_first_parameter_name = first_parameter_name;
|
||||
builder.pop_scope()
|
||||
},
|
||||
);
|
||||
// The default value of the parameters needs to be evaluated in the
|
||||
// enclosing scope.
|
||||
for default in parameters
|
||||
@@ -1422,6 +1470,29 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
|
||||
if let ast::Expr::Name(name) = &*node.target {
|
||||
let symbol_id = self.add_symbol(name.id.clone());
|
||||
let symbol = self.current_place_table().place_expr(symbol_id);
|
||||
// Check whether the variable has been declared global.
|
||||
if symbol.is_marked_global() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::AnnotatedGlobal(name.id.as_str().into()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
// Check whether the variable has been declared nonlocal.
|
||||
if symbol.is_marked_nonlocal() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::AnnotatedNonlocal(
|
||||
name.id.as_str().into(),
|
||||
),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// See https://docs.python.org/3/library/ast.html#ast.AnnAssign
|
||||
if matches!(
|
||||
*node.target,
|
||||
@@ -1862,8 +1933,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
}) => {
|
||||
for name in names {
|
||||
let symbol_id = self.add_symbol(name.id.clone());
|
||||
let symbol_table = self.current_place_table();
|
||||
let symbol = symbol_table.place_expr(symbol_id);
|
||||
let symbol = self.current_place_table().place_expr(symbol_id);
|
||||
// Check whether the variable has already been accessed in this scope.
|
||||
if symbol.is_bound() || symbol.is_declared() || symbol.is_used() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration {
|
||||
@@ -1874,11 +1945,112 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
let scope_id = self.current_scope();
|
||||
self.globals_by_scope
|
||||
.entry(scope_id)
|
||||
.or_default()
|
||||
.insert(symbol_id);
|
||||
// Check whether the variable has also been declared nonlocal.
|
||||
if symbol.is_marked_nonlocal() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
self.current_place_table_mut().mark_place_global(symbol_id);
|
||||
}
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
ast::Stmt::Nonlocal(ast::StmtNonlocal {
|
||||
range: _,
|
||||
node_index: _,
|
||||
names,
|
||||
}) => {
|
||||
for name in names {
|
||||
let local_scoped_place_id = self.add_symbol(name.id.clone());
|
||||
let local_place = self.current_place_table().place_expr(local_scoped_place_id);
|
||||
// Check whether the variable has already been accessed in this scope.
|
||||
if local_place.is_bound() || local_place.is_declared() || local_place.is_used()
|
||||
{
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration {
|
||||
name: name.to_string(),
|
||||
start: name.range.start(),
|
||||
},
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
// Check whether the variable has also been declared global.
|
||||
if local_place.is_marked_global() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
// The name is required to exist in an enclosing scope, but that definition
|
||||
// might come later. For example, this is example legal:
|
||||
//
|
||||
// ```py
|
||||
// def f():
|
||||
// def g():
|
||||
// nonlocal x
|
||||
// x = 1
|
||||
// ```
|
||||
//
|
||||
// To handle cases like this, we have to walk `x = 1` before we walk `nonlocal
|
||||
// x`. In other words, walking function bodies must be "deferred" to the end of
|
||||
// the scope where they're defined. See the `FunctionDef` branch above.
|
||||
let name_expr = PlaceExpr::name(name.id.clone());
|
||||
let mut found_matching_definition = false;
|
||||
for enclosing_scope_info in self.scope_stack.iter().rev().skip(1) {
|
||||
let enclosing_scope = &self.scopes[enclosing_scope_info.file_scope_id];
|
||||
if !enclosing_scope.kind().is_function_like() {
|
||||
// Skip over class scopes and the global scope.
|
||||
continue;
|
||||
}
|
||||
let enclosing_place_table =
|
||||
&self.place_tables[enclosing_scope_info.file_scope_id];
|
||||
let Some(enclosing_scoped_place_id) =
|
||||
enclosing_place_table.place_id_by_expr(&name_expr)
|
||||
else {
|
||||
// This name isn't defined in this scope. Keep going.
|
||||
continue;
|
||||
};
|
||||
let enclosing_place =
|
||||
enclosing_place_table.place_expr(enclosing_scoped_place_id);
|
||||
// We've found a definition for this name in an enclosing function-like
|
||||
// scope. Either this definition is the valid place this name refers to, or
|
||||
// else we'll emit a syntax error. Either way, we won't walk any more
|
||||
// enclosing scopes. Note that there are differences here compared to
|
||||
// `infer_place_load`: A regular load (e.g. `print(x)`) is allowed to refer
|
||||
// to a global variable (e.g. `x = 1` in the global scope), and similarly
|
||||
// it's allowed to refer to a variable in an enclosing function that's
|
||||
// declared `global` (e.g. `global x`). However, the `nonlocal` keyword
|
||||
// can't refer to global variables (that's a `SyntaxError`), and it also
|
||||
// can't refer to variables in enclosing functions that are declared
|
||||
// `global` (also a `SyntaxError`).
|
||||
if enclosing_place.is_marked_global() {
|
||||
// A "chain" of `nonlocal` statements is "broken" by a `global`
|
||||
// statement. Stop looping and report that this `nonlocal` statement is
|
||||
// invalid.
|
||||
break;
|
||||
}
|
||||
// We found a definition, and we've checked that that place isn't declared
|
||||
// `global` in its scope, but it's ok if it's `nonlocal`. If a chain of
|
||||
// `nonlocal` statements fails to lead to a valid binding, the outermost
|
||||
// one will be an error; we don't need to report an error for each one.
|
||||
found_matching_definition = true;
|
||||
self.current_place_table_mut()
|
||||
.mark_place_nonlocal(local_scoped_place_id);
|
||||
break;
|
||||
}
|
||||
if !found_matching_definition {
|
||||
// There's no matching definition in an enclosing scope. This `nonlocal`
|
||||
// statement is invalid.
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::InvalidNonlocal(name.to_string()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
}
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
@@ -1892,7 +2064,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
for target in targets {
|
||||
if let Ok(target) = PlaceExpr::try_from(target) {
|
||||
let place_id = self.add_place(PlaceExprWithFlags::new(target));
|
||||
self.current_place_table().mark_place_used(place_id);
|
||||
self.current_place_table_mut().mark_place_used(place_id);
|
||||
self.delete_binding(place_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,6 @@ pub struct Definition<'db> {
|
||||
|
||||
/// This is a dedicated field to avoid accessing `kind` to compute this value.
|
||||
pub(crate) is_reexported: bool,
|
||||
|
||||
count: countme::Count<Definition<'static>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
|
||||
@@ -58,8 +58,6 @@ pub(crate) struct Expression<'db> {
|
||||
|
||||
/// Should this expression be inferred as a normal expression or a type expression?
|
||||
pub(crate) kind: ExpressionKind,
|
||||
|
||||
count: countme::Count<Expression<'static>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
|
||||
@@ -330,6 +330,16 @@ impl PlaceExprWithFlags {
|
||||
self.flags.contains(PlaceFlags::IS_DECLARED)
|
||||
}
|
||||
|
||||
/// Is the place `global` its containing scope?
|
||||
pub fn is_marked_global(&self) -> bool {
|
||||
self.flags.contains(PlaceFlags::MARKED_GLOBAL)
|
||||
}
|
||||
|
||||
/// Is the place `nonlocal` its containing scope?
|
||||
pub fn is_marked_nonlocal(&self) -> bool {
|
||||
self.flags.contains(PlaceFlags::MARKED_NONLOCAL)
|
||||
}
|
||||
|
||||
pub(crate) fn as_name(&self) -> Option<&Name> {
|
||||
self.expr.as_name()
|
||||
}
|
||||
@@ -397,9 +407,7 @@ bitflags! {
|
||||
const IS_USED = 1 << 0;
|
||||
const IS_BOUND = 1 << 1;
|
||||
const IS_DECLARED = 1 << 2;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_GLOBAL = 1 << 3;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_NONLOCAL = 1 << 4;
|
||||
const IS_INSTANCE_ATTRIBUTE = 1 << 5;
|
||||
}
|
||||
@@ -441,8 +449,6 @@ pub struct ScopeId<'db> {
|
||||
pub file: File,
|
||||
|
||||
pub file_scope_id: FileScopeId,
|
||||
|
||||
count: countme::Count<ScopeId<'static>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
@@ -665,7 +671,7 @@ impl PlaceTable {
|
||||
}
|
||||
|
||||
/// Returns the place named `name`.
|
||||
#[allow(unused)] // used in tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn place_by_name(&self, name: &str) -> Option<&PlaceExprWithFlags> {
|
||||
let id = self.place_id_by_name(name)?;
|
||||
Some(self.place_expr(id))
|
||||
@@ -816,6 +822,14 @@ impl PlaceTableBuilder {
|
||||
self.table.places[id].insert_flags(PlaceFlags::IS_USED);
|
||||
}
|
||||
|
||||
pub(super) fn mark_place_global(&mut self, id: ScopedPlaceId) {
|
||||
self.table.places[id].insert_flags(PlaceFlags::MARKED_GLOBAL);
|
||||
}
|
||||
|
||||
pub(super) fn mark_place_nonlocal(&mut self, id: ScopedPlaceId) {
|
||||
self.table.places[id].insert_flags(PlaceFlags::MARKED_NONLOCAL);
|
||||
}
|
||||
|
||||
pub(super) fn places(&self) -> impl Iterator<Item = &PlaceExprWithFlags> {
|
||||
self.table.places()
|
||||
}
|
||||
|
||||
@@ -138,8 +138,6 @@ pub(crate) struct PatternPredicate<'db> {
|
||||
pub(crate) kind: PatternPredicateKind<'db>,
|
||||
|
||||
pub(crate) guard: Option<Expression<'db>>,
|
||||
|
||||
count: countme::Count<PatternPredicate<'static>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use infer::nearest_enclosing_class;
|
||||
use itertools::Either;
|
||||
use itertools::{Either, Itertools};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
|
||||
use std::slice::Iter;
|
||||
@@ -88,8 +88,7 @@ mod definition;
|
||||
#[cfg(test)]
|
||||
mod property_tests;
|
||||
|
||||
#[salsa::tracked(returns(ref), heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
|
||||
pub fn check_types(db: &dyn Db, file: File) -> Vec<Diagnostic> {
|
||||
let _span = tracing::trace_span!("check_types", ?file).entered();
|
||||
|
||||
tracing::debug!("Checking file '{path}'", path = file.path(db));
|
||||
@@ -111,7 +110,7 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
|
||||
|
||||
check_suppressions(db, file, &mut diagnostics);
|
||||
|
||||
diagnostics
|
||||
diagnostics.into_vec()
|
||||
}
|
||||
|
||||
/// Infer the type of a binding.
|
||||
@@ -818,7 +817,11 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
pub fn module_literal(db: &'db dyn Db, importing_file: File, submodule: &Module) -> Self {
|
||||
Self::ModuleLiteral(ModuleLiteralType::new(db, importing_file, submodule))
|
||||
Self::ModuleLiteral(ModuleLiteralType::new(
|
||||
db,
|
||||
submodule,
|
||||
submodule.kind().is_package().then_some(importing_file),
|
||||
))
|
||||
}
|
||||
|
||||
pub const fn into_module_literal(self) -> Option<ModuleLiteralType<'db>> {
|
||||
@@ -1215,10 +1218,11 @@ impl<'db> Type<'db> {
|
||||
|
||||
fn has_relation_to(self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation) -> bool {
|
||||
// Subtyping implies assignability, so if subtyping is reflexive and the two types are
|
||||
// equivalent, it is both a subtype and assignable. Assignability is always reflexive.
|
||||
if (relation.is_assignability() || self.subtyping_is_always_reflexive())
|
||||
&& self.is_equivalent_to(db, target)
|
||||
{
|
||||
// equal, it is both a subtype and assignable. Assignability is always reflexive.
|
||||
//
|
||||
// Note that we could do a full equivalence check here, but that would be both expensive
|
||||
// and unnecessary. This early return is only an optimisation.
|
||||
if (relation.is_assignability() || self.subtyping_is_always_reflexive()) && self == target {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1256,6 +1260,9 @@ impl<'db> Type<'db> {
|
||||
|
||||
// Two identical typevars must always solve to the same type, so they are always
|
||||
// subtypes of each other and assignable to each other.
|
||||
//
|
||||
// Note that this is not handled by the early return at the beginning of this method,
|
||||
// since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive.
|
||||
(Type::TypeVar(lhs_typevar), Type::TypeVar(rhs_typevar))
|
||||
if lhs_typevar == rhs_typevar =>
|
||||
{
|
||||
@@ -7499,20 +7506,51 @@ pub enum WrapperDescriptorKind {
|
||||
#[salsa::interned(debug)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct ModuleLiteralType<'db> {
|
||||
/// The file in which this module was imported.
|
||||
///
|
||||
/// We need this in order to know which submodules should be attached to it as attributes
|
||||
/// (because the submodules were also imported in this file).
|
||||
pub importing_file: File,
|
||||
|
||||
/// The imported module.
|
||||
pub module: Module,
|
||||
|
||||
/// The file in which this module was imported.
|
||||
///
|
||||
/// If the module is a module that could have submodules (a package),
|
||||
/// we need this in order to know which submodules should be attached to it as attributes
|
||||
/// (because the submodules were also imported in this file). For a package, this should
|
||||
/// therefore always be `Some()`. If the module is not a package, however, this should
|
||||
/// always be `None`: this helps reduce memory usage (the information is redundant for
|
||||
/// single-file modules), and ensures that two module-literal types that both refer to
|
||||
/// the same underlying single-file module are understood by ty as being equivalent types
|
||||
/// in all situations.
|
||||
_importing_file: Option<File>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for ModuleLiteralType<'_> {}
|
||||
|
||||
impl<'db> ModuleLiteralType<'db> {
|
||||
fn importing_file(self, db: &'db dyn Db) -> Option<File> {
|
||||
debug_assert_eq!(
|
||||
self._importing_file(db).is_some(),
|
||||
self.module(db).kind().is_package()
|
||||
);
|
||||
self._importing_file(db)
|
||||
}
|
||||
|
||||
fn available_submodule_attributes(&self, db: &'db dyn Db) -> impl Iterator<Item = Name> {
|
||||
self.importing_file(db)
|
||||
.into_iter()
|
||||
.flat_map(|file| imported_modules(db, file))
|
||||
.filter_map(|submodule_name| submodule_name.relative_to(self.module(db).name()))
|
||||
.filter_map(|relative_submodule| relative_submodule.components().next().map(Name::from))
|
||||
}
|
||||
|
||||
fn resolve_submodule(self, db: &'db dyn Db, name: &str) -> Option<Type<'db>> {
|
||||
let importing_file = self.importing_file(db)?;
|
||||
let relative_submodule_name = ModuleName::new(name)?;
|
||||
let mut absolute_submodule_name = self.module(db).name().clone();
|
||||
absolute_submodule_name.extend(&relative_submodule_name);
|
||||
let submodule = resolve_module(db, &absolute_submodule_name)?;
|
||||
Some(Type::module_literal(db, importing_file, &submodule))
|
||||
}
|
||||
|
||||
fn static_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
// `__dict__` is a very special member that is never overridden by module globals;
|
||||
// we should always look it up directly as an attribute on `types.ModuleType`,
|
||||
@@ -7532,16 +7570,9 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
// the parent module's `__init__.py` file being evaluated. That said, we have
|
||||
// chosen to always have the submodule take priority. (This matches pyright's
|
||||
// current behavior, but is the opposite of mypy's current behavior.)
|
||||
if let Some(submodule_name) = ModuleName::new(name) {
|
||||
let importing_file = self.importing_file(db);
|
||||
let imported_submodules = imported_modules(db, importing_file);
|
||||
let mut full_submodule_name = self.module(db).name().clone();
|
||||
full_submodule_name.extend(&submodule_name);
|
||||
if imported_submodules.contains(&full_submodule_name) {
|
||||
if let Some(submodule) = resolve_module(db, &full_submodule_name) {
|
||||
return Place::bound(Type::module_literal(db, importing_file, &submodule))
|
||||
.into();
|
||||
}
|
||||
if self.available_submodule_attributes(db).contains(name) {
|
||||
if let Some(submodule) = self.resolve_submodule(db, name) {
|
||||
return Place::bound(submodule).into();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7906,6 +7937,10 @@ impl<'db> UnionType<'db> {
|
||||
|
||||
/// Return `true` if `self` represents the exact same sets of possible runtime objects as `other`
|
||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
let self_elements = self.elements(db);
|
||||
let other_elements = other.elements(db);
|
||||
|
||||
@@ -7913,10 +7948,6 @@ impl<'db> UnionType<'db> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == other {
|
||||
@@ -7997,8 +8028,11 @@ impl<'db> IntersectionType<'db> {
|
||||
|
||||
/// Return `true` if `self` represents exactly the same set of possible runtime objects as `other`
|
||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
let self_positive = self.positive(db);
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
let self_positive = self.positive(db);
|
||||
let other_positive = other.positive(db);
|
||||
|
||||
if self_positive.len() != other_positive.len() {
|
||||
@@ -8006,17 +8040,12 @@ impl<'db> IntersectionType<'db> {
|
||||
}
|
||||
|
||||
let self_negative = self.negative(db);
|
||||
|
||||
let other_negative = other.negative(db);
|
||||
|
||||
if self_negative.len() != other_negative.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == other {
|
||||
|
||||
@@ -443,8 +443,13 @@ impl<'db> ClassType<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this == other,
|
||||
// A non-generic class is never equivalent to a generic class.
|
||||
// Two non-generic classes are only equivalent if they are equal (handled above).
|
||||
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false,
|
||||
|
||||
(ClassType::Generic(this), ClassType::Generic(other)) => {
|
||||
|
||||
@@ -1598,6 +1598,10 @@ impl TypeCheckDiagnostics {
|
||||
self.diagnostics.shrink_to_fit();
|
||||
}
|
||||
|
||||
pub(crate) fn into_vec(self) -> Vec<Diagnostic> {
|
||||
self.diagnostics
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> std::slice::Iter<'_, Diagnostic> {
|
||||
self.diagnostics.iter()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::module_resolver::resolve_module;
|
||||
use crate::place::{Place, imported_symbol, place_from_bindings, place_from_declarations};
|
||||
use crate::semantic_index::definition::DefinitionKind;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::{
|
||||
attribute_scopes, global_scope, imported_modules, place_table, semantic_index, use_def_map,
|
||||
attribute_scopes, global_scope, place_table, semantic_index, use_def_map,
|
||||
};
|
||||
use crate::types::{ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type};
|
||||
use crate::{Db, NameKind};
|
||||
@@ -197,24 +196,14 @@ impl<'db> AllMembers<'db> {
|
||||
});
|
||||
}
|
||||
|
||||
let module_name = module.name();
|
||||
self.members.extend(
|
||||
imported_modules(db, literal.importing_file(db))
|
||||
.iter()
|
||||
.filter_map(|submodule_name| {
|
||||
let module = resolve_module(db, submodule_name)?;
|
||||
let ty = Type::module_literal(db, file, &module);
|
||||
Some((submodule_name, ty))
|
||||
})
|
||||
.filter_map(|(submodule_name, ty)| {
|
||||
let relative = submodule_name.relative_to(module_name)?;
|
||||
Some((relative, ty))
|
||||
})
|
||||
.filter_map(|(relative_submodule_name, ty)| {
|
||||
let name = Name::from(relative_submodule_name.components().next()?);
|
||||
self.members
|
||||
.extend(literal.available_submodule_attributes(db).filter_map(
|
||||
|submodule_name| {
|
||||
let ty = literal.resolve_submodule(db, &submodule_name)?;
|
||||
let name = submodule_name.clone();
|
||||
Some(Member { name, ty })
|
||||
}),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1564,6 +1564,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let mut bound_ty = ty;
|
||||
|
||||
let global_use_def_map = self.index.use_def_map(FileScopeId::global());
|
||||
let nonlocal_use_def_map;
|
||||
let place_id = binding.place(self.db());
|
||||
let place = place_table.place_expr(place_id);
|
||||
let skip_non_global_scopes = self.skip_non_global_scopes(file_scope_id, place_id);
|
||||
@@ -1574,9 +1575,58 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.place_id_by_expr(&place.expr)
|
||||
{
|
||||
Some(id) => (global_use_def_map.end_of_scope_declarations(id), false),
|
||||
// This case is a syntax error (load before global declaration) but ignore that here
|
||||
// This variable shows up in `global` declarations but doesn't have an explicit
|
||||
// binding in the global scope.
|
||||
None => (use_def.declarations_at_binding(binding), true),
|
||||
}
|
||||
} else if self
|
||||
.index
|
||||
.symbol_is_nonlocal_in_scope(place_id, file_scope_id)
|
||||
{
|
||||
// If we run out of ancestor scopes without finding a definition, we'll fall back to
|
||||
// the local scope. This will also be a syntax error in `infer_nonlocal_statement` (no
|
||||
// binding for `nonlocal` found), but ignore that here.
|
||||
let mut declarations = use_def.declarations_at_binding(binding);
|
||||
let mut is_local = true;
|
||||
// Walk up parent scopes looking for the enclosing scope that has definition of this
|
||||
// name. `ancestor_scopes` includes the current scope, so skip that one.
|
||||
for (enclosing_scope_file_id, enclosing_scope) in
|
||||
self.index.ancestor_scopes(file_scope_id).skip(1)
|
||||
{
|
||||
// Ignore class scopes and the global scope.
|
||||
if !enclosing_scope.kind().is_function_like() {
|
||||
continue;
|
||||
}
|
||||
let enclosing_place_table = self.index.place_table(enclosing_scope_file_id);
|
||||
let Some(enclosing_place_id) = enclosing_place_table.place_id_by_expr(&place.expr)
|
||||
else {
|
||||
// This ancestor scope doesn't have a binding. Keep going.
|
||||
continue;
|
||||
};
|
||||
if self
|
||||
.index
|
||||
.symbol_is_nonlocal_in_scope(enclosing_place_id, enclosing_scope_file_id)
|
||||
{
|
||||
// The variable is `nonlocal` in this ancestor scope. Keep going.
|
||||
continue;
|
||||
}
|
||||
if self
|
||||
.index
|
||||
.symbol_is_global_in_scope(enclosing_place_id, enclosing_scope_file_id)
|
||||
{
|
||||
// The variable is `global` in this ancestor scope. This breaks the `nonlocal`
|
||||
// chain, and it's a syntax error in `infer_nonlocal_statement`. Ignore that
|
||||
// here and just bail out of this loop.
|
||||
break;
|
||||
}
|
||||
// We found the closest definition. Note that (unlike in `infer_place_load`) this
|
||||
// does *not* need to be a binding. It could be just `x: int`.
|
||||
nonlocal_use_def_map = self.index.use_def_map(enclosing_scope_file_id);
|
||||
declarations = nonlocal_use_def_map.end_of_scope_declarations(enclosing_place_id);
|
||||
is_local = false;
|
||||
break;
|
||||
}
|
||||
(declarations, is_local)
|
||||
} else {
|
||||
(use_def.declarations_at_binding(binding), true)
|
||||
};
|
||||
@@ -5800,13 +5850,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
let current_file = self.file();
|
||||
|
||||
let mut is_nonlocal_binding = false;
|
||||
if let Some(name) = expr.as_name() {
|
||||
let skip_non_global_scopes = place_table
|
||||
.place_id_by_name(name)
|
||||
.is_some_and(|symbol_id| self.skip_non_global_scopes(file_scope_id, symbol_id));
|
||||
|
||||
if skip_non_global_scopes {
|
||||
return global_symbol(self.db(), self.file(), name);
|
||||
if let Some(symbol_id) = place_table.place_id_by_name(name) {
|
||||
if self.skip_non_global_scopes(file_scope_id, symbol_id) {
|
||||
return global_symbol(self.db(), self.file(), name);
|
||||
}
|
||||
is_nonlocal_binding = self
|
||||
.index
|
||||
.symbol_is_nonlocal_in_scope(symbol_id, file_scope_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5819,7 +5871,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// a local variable or not in function-like scopes. If a variable has any bindings in a
|
||||
// function-like scope, it is considered a local variable; it never references another
|
||||
// scope. (At runtime, it would use the `LOAD_FAST` opcode.)
|
||||
if has_bindings_in_this_scope && scope.is_function_like(db) {
|
||||
if has_bindings_in_this_scope && scope.is_function_like(db) && !is_nonlocal_binding {
|
||||
return Place::Unbound.into();
|
||||
}
|
||||
|
||||
@@ -10035,7 +10087,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_diagnostic_messages(diagnostics: &TypeCheckDiagnostics, expected: &[&str]) {
|
||||
fn assert_diagnostic_messages(diagnostics: &[Diagnostic], expected: &[&str]) {
|
||||
let messages: Vec<&str> = diagnostics
|
||||
.iter()
|
||||
.map(Diagnostic::primary_message)
|
||||
@@ -10048,7 +10100,7 @@ mod tests {
|
||||
let file = system_path_to_file(db, filename).unwrap();
|
||||
let diagnostics = check_types(db, file);
|
||||
|
||||
assert_diagnostic_messages(diagnostics, expected);
|
||||
assert_diagnostic_messages(&diagnostics, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -44,8 +44,6 @@ pub(crate) struct Unpack<'db> {
|
||||
/// The ingredient representing the value expression of the unpacking. For example, in
|
||||
/// `(a, b) = (1, 2)`, the value expression is `(1, 2)`.
|
||||
pub(crate) value: UnpackValue<'db>,
|
||||
|
||||
count: countme::Count<Unpack<'static>>,
|
||||
}
|
||||
|
||||
impl<'db> Unpack<'db> {
|
||||
|
||||
@@ -340,7 +340,7 @@ fn run_test(
|
||||
Err(failures) => return Some(failures),
|
||||
};
|
||||
|
||||
diagnostics.extend(type_diagnostics.into_iter().cloned());
|
||||
diagnostics.extend(type_diagnostics);
|
||||
diagnostics.sort_by(|left, right| {
|
||||
left.rendering_sort_key(db)
|
||||
.cmp(&right.rendering_sort_key(db))
|
||||
|
||||
@@ -309,6 +309,7 @@ impl Workspace {
|
||||
Ok(completions
|
||||
.into_iter()
|
||||
.map(|completion| Completion {
|
||||
kind: completion.kind(&self.db).map(CompletionKind::from),
|
||||
name: completion.name.into(),
|
||||
})
|
||||
.collect())
|
||||
@@ -338,6 +339,52 @@ impl Workspace {
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "semanticTokens")]
|
||||
pub fn semantic_tokens(&self, file_id: &FileHandle) -> Result<Vec<SemanticToken>, Error> {
|
||||
let index = line_index(&self.db, file_id.file);
|
||||
let source = source_text(&self.db, file_id.file);
|
||||
|
||||
let semantic_token = ty_ide::semantic_tokens(&self.db, file_id.file, None);
|
||||
|
||||
let result = semantic_token
|
||||
.iter()
|
||||
.map(|token| SemanticToken {
|
||||
kind: token.token_type.into(),
|
||||
modifiers: token.modifiers.bits(),
|
||||
range: Range::from_text_range(token.range, &index, &source, self.position_encoding),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "semanticTokensInRange")]
|
||||
pub fn semantic_tokens_in_range(
|
||||
&self,
|
||||
file_id: &FileHandle,
|
||||
range: Range,
|
||||
) -> Result<Vec<SemanticToken>, Error> {
|
||||
let index = line_index(&self.db, file_id.file);
|
||||
let source = source_text(&self.db, file_id.file);
|
||||
|
||||
let semantic_token = ty_ide::semantic_tokens(
|
||||
&self.db,
|
||||
file_id.file,
|
||||
Some(range.to_text_range(&index, &source, self.position_encoding)?),
|
||||
);
|
||||
|
||||
let result = semantic_token
|
||||
.iter()
|
||||
.map(|token| SemanticToken {
|
||||
kind: token.token_type.into(),
|
||||
modifiers: token.modifiers.bits(),
|
||||
range: Range::from_text_range(token.range, &index, &source, self.position_encoding),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
|
||||
@@ -620,6 +667,69 @@ pub struct Hover {
|
||||
pub struct Completion {
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub name: String,
|
||||
pub kind: Option<CompletionKind>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CompletionKind {
|
||||
Text,
|
||||
Method,
|
||||
Function,
|
||||
Constructor,
|
||||
Field,
|
||||
Variable,
|
||||
Class,
|
||||
Interface,
|
||||
Module,
|
||||
Property,
|
||||
Unit,
|
||||
Value,
|
||||
Enum,
|
||||
Keyword,
|
||||
Snippet,
|
||||
Color,
|
||||
File,
|
||||
Reference,
|
||||
Folder,
|
||||
EnumMember,
|
||||
Constant,
|
||||
Struct,
|
||||
Event,
|
||||
Operator,
|
||||
TypeParameter,
|
||||
}
|
||||
|
||||
impl From<ty_python_semantic::CompletionKind> for CompletionKind {
|
||||
fn from(value: ty_python_semantic::CompletionKind) -> Self {
|
||||
match value {
|
||||
ty_python_semantic::CompletionKind::Text => Self::Text,
|
||||
ty_python_semantic::CompletionKind::Method => Self::Method,
|
||||
ty_python_semantic::CompletionKind::Function => Self::Function,
|
||||
ty_python_semantic::CompletionKind::Constructor => Self::Constructor,
|
||||
ty_python_semantic::CompletionKind::Field => Self::Field,
|
||||
ty_python_semantic::CompletionKind::Variable => Self::Variable,
|
||||
ty_python_semantic::CompletionKind::Class => Self::Class,
|
||||
ty_python_semantic::CompletionKind::Interface => Self::Interface,
|
||||
ty_python_semantic::CompletionKind::Module => Self::Module,
|
||||
ty_python_semantic::CompletionKind::Property => Self::Property,
|
||||
ty_python_semantic::CompletionKind::Unit => Self::Unit,
|
||||
ty_python_semantic::CompletionKind::Value => Self::Value,
|
||||
ty_python_semantic::CompletionKind::Enum => Self::Enum,
|
||||
ty_python_semantic::CompletionKind::Keyword => Self::Keyword,
|
||||
ty_python_semantic::CompletionKind::Snippet => Self::Snippet,
|
||||
ty_python_semantic::CompletionKind::Color => Self::Color,
|
||||
ty_python_semantic::CompletionKind::File => Self::File,
|
||||
ty_python_semantic::CompletionKind::Reference => Self::Reference,
|
||||
ty_python_semantic::CompletionKind::Folder => Self::Folder,
|
||||
ty_python_semantic::CompletionKind::EnumMember => Self::EnumMember,
|
||||
ty_python_semantic::CompletionKind::Constant => Self::Constant,
|
||||
ty_python_semantic::CompletionKind::Struct => Self::Struct,
|
||||
ty_python_semantic::CompletionKind::Event => Self::Event,
|
||||
ty_python_semantic::CompletionKind::Operator => Self::Operator,
|
||||
ty_python_semantic::CompletionKind::TypeParameter => Self::TypeParameter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -631,6 +741,74 @@ pub struct InlayHint {
|
||||
pub position: Position,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SemanticToken {
|
||||
pub kind: SemanticTokenKind,
|
||||
pub modifiers: u32,
|
||||
pub range: Range,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SemanticToken {
|
||||
pub fn kinds() -> Vec<String> {
|
||||
ty_ide::SemanticTokenType::all()
|
||||
.iter()
|
||||
.map(|ty| ty.as_lsp_concept().to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn modifiers() -> Vec<String> {
|
||||
ty_ide::SemanticTokenModifier::all_names()
|
||||
.iter()
|
||||
.map(|name| (*name).to_string())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum SemanticTokenKind {
|
||||
Namespace,
|
||||
Class,
|
||||
Parameter,
|
||||
SelfParameter,
|
||||
ClsParameter,
|
||||
Variable,
|
||||
Property,
|
||||
Function,
|
||||
Method,
|
||||
Keyword,
|
||||
String,
|
||||
Number,
|
||||
Decorator,
|
||||
BuiltinConstant,
|
||||
TypeParameter,
|
||||
}
|
||||
|
||||
impl From<ty_ide::SemanticTokenType> for SemanticTokenKind {
|
||||
fn from(value: ty_ide::SemanticTokenType) -> Self {
|
||||
match value {
|
||||
ty_ide::SemanticTokenType::Namespace => Self::Namespace,
|
||||
ty_ide::SemanticTokenType::Class => Self::Class,
|
||||
ty_ide::SemanticTokenType::Parameter => Self::Parameter,
|
||||
ty_ide::SemanticTokenType::SelfParameter => Self::SelfParameter,
|
||||
ty_ide::SemanticTokenType::ClsParameter => Self::ClsParameter,
|
||||
ty_ide::SemanticTokenType::Variable => Self::Variable,
|
||||
ty_ide::SemanticTokenType::Property => Self::Property,
|
||||
ty_ide::SemanticTokenType::Function => Self::Function,
|
||||
ty_ide::SemanticTokenType::Method => Self::Method,
|
||||
ty_ide::SemanticTokenType::Keyword => Self::Keyword,
|
||||
ty_ide::SemanticTokenType::String => Self::String,
|
||||
ty_ide::SemanticTokenType::Number => Self::Number,
|
||||
ty_ide::SemanticTokenType::Decorator => Self::Decorator,
|
||||
ty_ide::SemanticTokenType::BuiltinConstant => Self::BuiltinConstant,
|
||||
ty_ide::SemanticTokenType::TypeParameter => Self::TypeParameter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct WasmSystem {
|
||||
fs: MemoryFileSystem,
|
||||
|
||||
@@ -287,11 +287,7 @@ function defineAyuThemes(monaco: Monaco) {
|
||||
token: "comment",
|
||||
},
|
||||
{
|
||||
foreground: ROCK,
|
||||
token: "string",
|
||||
},
|
||||
{
|
||||
foreground: SUN,
|
||||
foreground: COSMIC,
|
||||
token: "keyword",
|
||||
},
|
||||
{
|
||||
@@ -302,6 +298,22 @@ function defineAyuThemes(monaco: Monaco) {
|
||||
token: "tag",
|
||||
foreground: ROCK,
|
||||
},
|
||||
{
|
||||
foreground: ROCK,
|
||||
token: "string",
|
||||
},
|
||||
{
|
||||
token: "method",
|
||||
foreground: SUN,
|
||||
},
|
||||
{
|
||||
token: "function",
|
||||
foreground: SUN,
|
||||
},
|
||||
{
|
||||
token: "decorator",
|
||||
foreground: SUN,
|
||||
},
|
||||
],
|
||||
encodedTokensColors: [],
|
||||
});
|
||||
@@ -548,11 +560,11 @@ function defineAyuThemes(monaco: Monaco) {
|
||||
token: "comment",
|
||||
},
|
||||
{
|
||||
foreground: RADIATE,
|
||||
foreground: ELECTRON,
|
||||
token: "string",
|
||||
},
|
||||
{
|
||||
foreground: ELECTRON,
|
||||
foreground: CONSTELLATION,
|
||||
token: "number",
|
||||
},
|
||||
{
|
||||
@@ -560,7 +572,7 @@ function defineAyuThemes(monaco: Monaco) {
|
||||
token: "identifier",
|
||||
},
|
||||
{
|
||||
foreground: SUN,
|
||||
foreground: RADIATE,
|
||||
token: "keyword",
|
||||
},
|
||||
{
|
||||
@@ -571,6 +583,30 @@ function defineAyuThemes(monaco: Monaco) {
|
||||
foreground: ASTEROID,
|
||||
token: "delimiter",
|
||||
},
|
||||
{
|
||||
token: "class",
|
||||
foreground: SUPERNOVA,
|
||||
},
|
||||
{
|
||||
foreground: STARLIGHT,
|
||||
token: "variable",
|
||||
},
|
||||
{
|
||||
foreground: STARLIGHT,
|
||||
token: "parameter",
|
||||
},
|
||||
{
|
||||
token: "method",
|
||||
foreground: SUN,
|
||||
},
|
||||
{
|
||||
token: "function",
|
||||
foreground: SUN,
|
||||
},
|
||||
{
|
||||
token: "decorator",
|
||||
foreground: SUN,
|
||||
},
|
||||
],
|
||||
encodedTokensColors: [],
|
||||
});
|
||||
|
||||
@@ -20,8 +20,10 @@ import { Theme } from "shared";
|
||||
import {
|
||||
Position as TyPosition,
|
||||
Range as TyRange,
|
||||
SemanticToken,
|
||||
Severity,
|
||||
type Workspace,
|
||||
CompletionKind,
|
||||
} from "ty_wasm";
|
||||
import { FileId, ReadonlyFiles } from "../Playground";
|
||||
import { isPythonFile } from "./Files";
|
||||
@@ -123,6 +125,7 @@ export default function Editor({
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
contextmenu: true,
|
||||
"semanticHighlighting.enabled": true,
|
||||
}}
|
||||
language={fileName.endsWith(".pyi") ? "python" : undefined}
|
||||
path={fileName}
|
||||
@@ -147,7 +150,9 @@ class PlaygroundServer
|
||||
languages.HoverProvider,
|
||||
languages.InlayHintsProvider,
|
||||
languages.DocumentFormattingEditProvider,
|
||||
languages.CompletionItemProvider
|
||||
languages.CompletionItemProvider,
|
||||
languages.DocumentSemanticTokensProvider,
|
||||
languages.DocumentRangeSemanticTokensProvider
|
||||
{
|
||||
private typeDefinitionProviderDisposable: IDisposable;
|
||||
private editorOpenerDisposable: IDisposable;
|
||||
@@ -155,6 +160,8 @@ class PlaygroundServer
|
||||
private inlayHintsDisposable: IDisposable;
|
||||
private formatDisposable: IDisposable;
|
||||
private completionDisposable: IDisposable;
|
||||
private semanticTokensDisposable: IDisposable;
|
||||
private rangeSemanticTokensDisposable: IDisposable;
|
||||
|
||||
constructor(
|
||||
private monaco: Monaco,
|
||||
@@ -174,6 +181,13 @@ class PlaygroundServer
|
||||
"python",
|
||||
this,
|
||||
);
|
||||
this.semanticTokensDisposable =
|
||||
monaco.languages.registerDocumentSemanticTokensProvider("python", this);
|
||||
this.rangeSemanticTokensDisposable =
|
||||
monaco.languages.registerDocumentRangeSemanticTokensProvider(
|
||||
"python",
|
||||
this,
|
||||
);
|
||||
this.editorOpenerDisposable = monaco.editor.registerEditorOpener(this);
|
||||
this.formatDisposable =
|
||||
monaco.languages.registerDocumentFormattingEditProvider("python", this);
|
||||
@@ -181,6 +195,60 @@ class PlaygroundServer
|
||||
|
||||
triggerCharacters: string[] = ["."];
|
||||
|
||||
getLegend(): languages.SemanticTokensLegend {
|
||||
return {
|
||||
tokenTypes: SemanticToken.kinds(),
|
||||
tokenModifiers: SemanticToken.modifiers(),
|
||||
};
|
||||
}
|
||||
|
||||
provideDocumentSemanticTokens(
|
||||
model: editor.ITextModel,
|
||||
): languages.SemanticTokens | null {
|
||||
const selectedFile = this.props.files.selected;
|
||||
|
||||
if (selectedFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedHandle = this.props.files.handles[selectedFile];
|
||||
|
||||
if (selectedHandle == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokens = this.props.workspace.semanticTokens(selectedHandle);
|
||||
return generateMonacoTokens(tokens, model);
|
||||
}
|
||||
|
||||
releaseDocumentSemanticTokens() {}
|
||||
|
||||
provideDocumentRangeSemanticTokens(
|
||||
model: editor.ITextModel,
|
||||
range: Range,
|
||||
): languages.SemanticTokens | null {
|
||||
const selectedFile = this.props.files.selected;
|
||||
|
||||
if (selectedFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedHandle = this.props.files.handles[selectedFile];
|
||||
|
||||
if (selectedHandle == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tyRange = monacoRangeToTyRange(range);
|
||||
|
||||
const tokens = this.props.workspace.semanticTokensInRange(
|
||||
selectedHandle,
|
||||
tyRange,
|
||||
);
|
||||
|
||||
return generateMonacoTokens(tokens, model);
|
||||
}
|
||||
|
||||
provideCompletionItems(
|
||||
model: editor.ITextModel,
|
||||
position: Position,
|
||||
@@ -209,7 +277,10 @@ class PlaygroundServer
|
||||
suggestions: completions.map((completion, i) => ({
|
||||
label: completion.name,
|
||||
sortText: String(i).padStart(digitsLength, "0"),
|
||||
kind: CompletionItemKind.Variable,
|
||||
kind:
|
||||
completion.kind == null
|
||||
? CompletionItemKind.Variable
|
||||
: mapCompletionKind(completion.kind),
|
||||
insertText: completion.name,
|
||||
// TODO(micha): It's unclear why this field is required for monaco but not VS Code.
|
||||
// and omitting it works just fine? The LSP doesn't expose this information right now
|
||||
@@ -495,6 +566,8 @@ class PlaygroundServer
|
||||
this.typeDefinitionProviderDisposable.dispose();
|
||||
this.inlayHintsDisposable.dispose();
|
||||
this.formatDisposable.dispose();
|
||||
this.rangeSemanticTokensDisposable.dispose();
|
||||
this.semanticTokensDisposable.dispose();
|
||||
this.completionDisposable.dispose();
|
||||
}
|
||||
}
|
||||
@@ -514,3 +587,91 @@ function monacoRangeToTyRange(range: IRange): TyRange {
|
||||
new TyPosition(range.endLineNumber, range.endColumn),
|
||||
);
|
||||
}
|
||||
|
||||
function generateMonacoTokens(
|
||||
semantic: SemanticToken[],
|
||||
model: editor.ITextModel,
|
||||
): languages.SemanticTokens {
|
||||
const result = [];
|
||||
|
||||
let prevLine = 0;
|
||||
let prevChar = 0;
|
||||
|
||||
for (const token of semantic) {
|
||||
// Convert from 1-based to 0-based indexing for Monaco
|
||||
const line = token.range.start.line - 1;
|
||||
const char = token.range.start.column - 1;
|
||||
|
||||
const length = model.getValueLengthInRange(
|
||||
tyRangeToMonacoRange(token.range),
|
||||
);
|
||||
|
||||
result.push(
|
||||
line - prevLine,
|
||||
prevLine === line ? char - prevChar : char,
|
||||
length,
|
||||
token.kind,
|
||||
token.modifiers,
|
||||
);
|
||||
|
||||
prevLine = line;
|
||||
prevChar = char;
|
||||
}
|
||||
|
||||
return { data: Uint32Array.from(result) };
|
||||
}
|
||||
|
||||
function mapCompletionKind(kind: CompletionKind): CompletionItemKind {
|
||||
switch (kind) {
|
||||
case CompletionKind.Text:
|
||||
return CompletionItemKind.Text;
|
||||
case CompletionKind.Method:
|
||||
return CompletionItemKind.Method;
|
||||
case CompletionKind.Function:
|
||||
return CompletionItemKind.Function;
|
||||
case CompletionKind.Constructor:
|
||||
return CompletionItemKind.Constructor;
|
||||
case CompletionKind.Field:
|
||||
return CompletionItemKind.Field;
|
||||
case CompletionKind.Variable:
|
||||
return CompletionItemKind.Variable;
|
||||
case CompletionKind.Class:
|
||||
return CompletionItemKind.Class;
|
||||
case CompletionKind.Interface:
|
||||
return CompletionItemKind.Interface;
|
||||
case CompletionKind.Module:
|
||||
return CompletionItemKind.Module;
|
||||
case CompletionKind.Property:
|
||||
return CompletionItemKind.Property;
|
||||
case CompletionKind.Unit:
|
||||
return CompletionItemKind.Unit;
|
||||
case CompletionKind.Value:
|
||||
return CompletionItemKind.Value;
|
||||
case CompletionKind.Enum:
|
||||
return CompletionItemKind.Enum;
|
||||
case CompletionKind.Keyword:
|
||||
return CompletionItemKind.Keyword;
|
||||
case CompletionKind.Snippet:
|
||||
return CompletionItemKind.Snippet;
|
||||
case CompletionKind.Color:
|
||||
return CompletionItemKind.Color;
|
||||
case CompletionKind.File:
|
||||
return CompletionItemKind.File;
|
||||
case CompletionKind.Reference:
|
||||
return CompletionItemKind.Reference;
|
||||
case CompletionKind.Folder:
|
||||
return CompletionItemKind.Folder;
|
||||
case CompletionKind.EnumMember:
|
||||
return CompletionItemKind.EnumMember;
|
||||
case CompletionKind.Constant:
|
||||
return CompletionItemKind.Constant;
|
||||
case CompletionKind.Struct:
|
||||
return CompletionItemKind.Struct;
|
||||
case CompletionKind.Event:
|
||||
return CompletionItemKind.Event;
|
||||
case CompletionKind.Operator:
|
||||
return CompletionItemKind.Operator;
|
||||
case CompletionKind.TypeParameter:
|
||||
return CompletionItemKind.TypeParameter;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user