pathlint 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pathlint-0.3.1/PKG-INFO +230 -0
- pathlint-0.3.1/README.md +200 -0
- pathlint-0.3.1/docs/cli.md +149 -0
- pathlint-0.3.1/docs/detection.md +48 -0
- pathlint-0.3.1/docs/file-selection.md +68 -0
- pathlint-0.3.1/docs/fixer.md +133 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/__init__.py +0 -5
- {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/__main__.py +1 -3
- pathlint-0.3.1/src/pathlint/_source.py +79 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/_version.py +2 -2
- pathlint-0.3.1/src/pathlint/autofix.py +283 -0
- pathlint-0.3.1/src/pathlint/bindings.py +147 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/cli.py +36 -55
- {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/discovery.py +177 -103
- pathlint-0.3.1/src/pathlint/edits.py +261 -0
- pathlint-0.3.1/src/pathlint/linter.py +507 -0
- pathlint-0.3.1/src/pathlint/mutations.py +212 -0
- pathlint-0.3.1/tests/__init__.py +1 -0
- pathlint-0.3.1/tests/_forms.py +29 -0
- pathlint-0.3.1/tests/conftest.py +45 -0
- pathlint-0.3.1/tests/helpers.py +42 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_cli.py +106 -109
- {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_detection.py +213 -141
- {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_discovery.py +177 -23
- {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_transformations.py +216 -264
- pathlint-0.3.0/PKG-INFO +0 -345
- pathlint-0.3.0/README.md +0 -315
- pathlint-0.3.0/src/pathlint/autofix.py +0 -833
- pathlint-0.3.0/src/pathlint/linter.py +0 -789
- pathlint-0.3.0/tests/test_autofix.py +0 -438
- pathlint-0.3.0/tests/test_linter.py +0 -408
- {pathlint-0.3.0 → pathlint-0.3.1}/.github/workflows/publish.yml +0 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/.github/workflows/test.yml +0 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/.gitignore +0 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/.pre-commit-config.yaml +0 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/.pre-commit-hooks.yaml +0 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/LICENSE.txt +0 -0
- {pathlint-0.3.0 → pathlint-0.3.1}/pyproject.toml +0 -0
pathlint-0.3.1/PKG-INFO
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pathlint
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Fast linter to detect os.path usage and encourage pathlib adoption
|
|
5
|
+
Project-URL: Repository, https://github.com/pszemraj/pathlint
|
|
6
|
+
Project-URL: Issues, https://github.com/pszemraj/pathlint/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/pszemraj/pathlint/releases
|
|
8
|
+
Project-URL: Documentation, https://github.com/pszemraj/pathlint#readme
|
|
9
|
+
Author-email: Peter Szemraj <peterszemraj+dev@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE.txt
|
|
12
|
+
Keywords: code-quality,linter,os.path,pathlib,pre-commit,python-linter
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# pathlint
|
|
32
|
+
|
|
33
|
+
Find `os.path` usage in Python code and migrate supported calls to `pathlib`.
|
|
34
|
+
|
|
35
|
+
## Example
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# Before
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
filename = os.path.join("src", "file.py")
|
|
42
|
+
|
|
43
|
+
# With pathlib (keeping a string result)
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
|
|
46
|
+
filename = str(Path("src") / "file.py")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Find places to migrate, then preview the changes pathlint can make:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pathlint check .
|
|
53
|
+
pathlint fix . --dry-run
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
Requires **Python 3.9+**. No runtime dependencies.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install pathlint
|
|
62
|
+
pathlint --version
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
Check a project, a directory, or individual files:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pathlint check .
|
|
71
|
+
pathlint check src/ tests/
|
|
72
|
+
pathlint check myfile.py
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Diagnostics point to the source line so you can review each use:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
src/utils.py:12: PL001 os.path usage: os.path.exists(value)
|
|
79
|
+
Found 1 violation in 1 of 42 files checked.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Preview experimental fixes without changing files:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pathlint fix . --dry-run
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For a file containing `import os` and the join call, the preview shows:
|
|
89
|
+
|
|
90
|
+
```diff
|
|
91
|
+
--- example.py
|
|
92
|
+
+++ example.py (fixed)
|
|
93
|
+
@@ -1,2 +1,3 @@
|
|
94
|
+
import os
|
|
95
|
+
-filename = os.path.join("src", "file.py")
|
|
96
|
+
+from pathlib import Path as _Path
|
|
97
|
+
+filename = str(_Path("src", "file.py"))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Review the diff before applying it, then run your tests:**
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pathlint fix .
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
After applying fixes, rerun `pathlint check .` to see which uses need
|
|
107
|
+
manual migration.
|
|
108
|
+
|
|
109
|
+
List the Python files that any of these commands would select:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pathlint files .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use this listing to confirm your exclusions before applying fixes; it selects
|
|
116
|
+
files without reading their Python source.
|
|
117
|
+
|
|
118
|
+
Paths default to the current directory. Use `pathlint --help` or
|
|
119
|
+
`pathlint check --help` to explore the CLI; `python -m pathlint check .` also
|
|
120
|
+
works when you prefer module invocation.
|
|
121
|
+
|
|
122
|
+
## What it detects
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
import os
|
|
126
|
+
import os.path as osp
|
|
127
|
+
from os.path import exists as present
|
|
128
|
+
|
|
129
|
+
os.path.exists("file")
|
|
130
|
+
osp.join("directory", "file")
|
|
131
|
+
present("file")
|
|
132
|
+
callback = os.path.join
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Pathlint resolves imports, aliases, and local shadowing, including usage in
|
|
136
|
+
annotations. Valid `os.PathLike` annotations are not flagged. Detection is
|
|
137
|
+
static: dynamic rebinding and wildcard imports may leave uses undetected.
|
|
138
|
+
See [detection details](docs/detection.md) for advanced cases.
|
|
139
|
+
|
|
140
|
+
## Auto-fix
|
|
141
|
+
|
|
142
|
+
**Fixing is experimental. Review every diff and run your tests.**
|
|
143
|
+
|
|
144
|
+
Only these five calls support automatic fixes, with string-literal arguments:
|
|
145
|
+
|
|
146
|
+
| Usage | Pathlib replacement |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `join` | `str(Path(...))` |
|
|
149
|
+
| `basename` | `Path(...).name` |
|
|
150
|
+
| `dirname` | `str(Path(...).parent)` |
|
|
151
|
+
| `isabs` | `Path(...).is_absolute()` |
|
|
152
|
+
| `expanduser` (without `~`) | `str(Path(...).expanduser())` |
|
|
153
|
+
| `exists`, `isfile`, `isdir` | Manual migration |
|
|
154
|
+
| `abspath`, `splitext`, `sep`, `pathsep` | Manual migration |
|
|
155
|
+
|
|
156
|
+
Fixes preserve surrounding comments and strings. Annotation usages are detected
|
|
157
|
+
but never rewritten.
|
|
158
|
+
|
|
159
|
+
The fixer is conservative: it rewrites calls only when its static checks allow
|
|
160
|
+
them and leaves uncertain cases unchanged. It does not fix every violation.
|
|
161
|
+
See [fixer limitations](docs/fixer.md) for skipped calls and path forms.
|
|
162
|
+
|
|
163
|
+
## Common options
|
|
164
|
+
|
|
165
|
+
Exclude directories or generated files; repeat `--exclude` / `-e` as needed:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pathlint check . --exclude vendor -e "*.generated.py"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Directory scans respect Git ignore rules when Git is available and skip common
|
|
172
|
+
generated directories by default. Preview selection with `pathlint files .`;
|
|
173
|
+
see [file-selection rules](docs/file-selection.md) for exact matching behavior.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Findings only, without a summary
|
|
177
|
+
pathlint check . --quiet
|
|
178
|
+
|
|
179
|
+
# Five files with the most violations
|
|
180
|
+
pathlint check . --stats
|
|
181
|
+
|
|
182
|
+
# Machine-readable report
|
|
183
|
+
pathlint check . --output-format json
|
|
184
|
+
|
|
185
|
+
# Fix preview without a summary or terminal colors
|
|
186
|
+
pathlint fix . --dry-run --quiet --no-color
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For scripting, `check` exits `0` when clean and `1` when it finds violations;
|
|
190
|
+
`fix --dry-run` exits `1` when it proposes edits. Errors exit `2`.
|
|
191
|
+
See the [CLI reference](docs/cli.md) for output formats, exit codes, and
|
|
192
|
+
migration from older commands.
|
|
193
|
+
|
|
194
|
+
## Pre-commit
|
|
195
|
+
|
|
196
|
+
Add to `.pre-commit-config.yaml`, pinning the release you want to use:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
repos:
|
|
200
|
+
- repo: https://github.com/pszemraj/pathlint
|
|
201
|
+
rev: v0.3.0
|
|
202
|
+
hooks:
|
|
203
|
+
- id: pathlint
|
|
204
|
+
args: [--exclude, vendor, --exclude, backups]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Install and run the hook:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
pre-commit install
|
|
211
|
+
pre-commit run pathlint --all-files
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The `pathlint` hook runs `pathlint check`. Use `pathlint-fix` instead to apply
|
|
215
|
+
experimental fixes, and review its changes before committing.
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
Install the development dependencies, run the tests, and check style:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip install -e '.[dev]'
|
|
223
|
+
pytest
|
|
224
|
+
ruff check .
|
|
225
|
+
ruff format --check .
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT License - see [LICENSE.txt](LICENSE.txt).
|
pathlint-0.3.1/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# pathlint
|
|
2
|
+
|
|
3
|
+
Find `os.path` usage in Python code and migrate supported calls to `pathlib`.
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
# Before
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
filename = os.path.join("src", "file.py")
|
|
12
|
+
|
|
13
|
+
# With pathlib (keeping a string result)
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
filename = str(Path("src") / "file.py")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Find places to migrate, then preview the changes pathlint can make:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pathlint check .
|
|
23
|
+
pathlint fix . --dry-run
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Requires **Python 3.9+**. No runtime dependencies.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install pathlint
|
|
32
|
+
pathlint --version
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
Check a project, a directory, or individual files:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pathlint check .
|
|
41
|
+
pathlint check src/ tests/
|
|
42
|
+
pathlint check myfile.py
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Diagnostics point to the source line so you can review each use:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
src/utils.py:12: PL001 os.path usage: os.path.exists(value)
|
|
49
|
+
Found 1 violation in 1 of 42 files checked.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Preview experimental fixes without changing files:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pathlint fix . --dry-run
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For a file containing `import os` and the join call, the preview shows:
|
|
59
|
+
|
|
60
|
+
```diff
|
|
61
|
+
--- example.py
|
|
62
|
+
+++ example.py (fixed)
|
|
63
|
+
@@ -1,2 +1,3 @@
|
|
64
|
+
import os
|
|
65
|
+
-filename = os.path.join("src", "file.py")
|
|
66
|
+
+from pathlib import Path as _Path
|
|
67
|
+
+filename = str(_Path("src", "file.py"))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Review the diff before applying it, then run your tests:**
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pathlint fix .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
After applying fixes, rerun `pathlint check .` to see which uses need
|
|
77
|
+
manual migration.
|
|
78
|
+
|
|
79
|
+
List the Python files that any of these commands would select:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pathlint files .
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use this listing to confirm your exclusions before applying fixes; it selects
|
|
86
|
+
files without reading their Python source.
|
|
87
|
+
|
|
88
|
+
Paths default to the current directory. Use `pathlint --help` or
|
|
89
|
+
`pathlint check --help` to explore the CLI; `python -m pathlint check .` also
|
|
90
|
+
works when you prefer module invocation.
|
|
91
|
+
|
|
92
|
+
## What it detects
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import os
|
|
96
|
+
import os.path as osp
|
|
97
|
+
from os.path import exists as present
|
|
98
|
+
|
|
99
|
+
os.path.exists("file")
|
|
100
|
+
osp.join("directory", "file")
|
|
101
|
+
present("file")
|
|
102
|
+
callback = os.path.join
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Pathlint resolves imports, aliases, and local shadowing, including usage in
|
|
106
|
+
annotations. Valid `os.PathLike` annotations are not flagged. Detection is
|
|
107
|
+
static: dynamic rebinding and wildcard imports may leave uses undetected.
|
|
108
|
+
See [detection details](docs/detection.md) for advanced cases.
|
|
109
|
+
|
|
110
|
+
## Auto-fix
|
|
111
|
+
|
|
112
|
+
**Fixing is experimental. Review every diff and run your tests.**
|
|
113
|
+
|
|
114
|
+
Only these five calls support automatic fixes, with string-literal arguments:
|
|
115
|
+
|
|
116
|
+
| Usage | Pathlib replacement |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `join` | `str(Path(...))` |
|
|
119
|
+
| `basename` | `Path(...).name` |
|
|
120
|
+
| `dirname` | `str(Path(...).parent)` |
|
|
121
|
+
| `isabs` | `Path(...).is_absolute()` |
|
|
122
|
+
| `expanduser` (without `~`) | `str(Path(...).expanduser())` |
|
|
123
|
+
| `exists`, `isfile`, `isdir` | Manual migration |
|
|
124
|
+
| `abspath`, `splitext`, `sep`, `pathsep` | Manual migration |
|
|
125
|
+
|
|
126
|
+
Fixes preserve surrounding comments and strings. Annotation usages are detected
|
|
127
|
+
but never rewritten.
|
|
128
|
+
|
|
129
|
+
The fixer is conservative: it rewrites calls only when its static checks allow
|
|
130
|
+
them and leaves uncertain cases unchanged. It does not fix every violation.
|
|
131
|
+
See [fixer limitations](docs/fixer.md) for skipped calls and path forms.
|
|
132
|
+
|
|
133
|
+
## Common options
|
|
134
|
+
|
|
135
|
+
Exclude directories or generated files; repeat `--exclude` / `-e` as needed:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pathlint check . --exclude vendor -e "*.generated.py"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Directory scans respect Git ignore rules when Git is available and skip common
|
|
142
|
+
generated directories by default. Preview selection with `pathlint files .`;
|
|
143
|
+
see [file-selection rules](docs/file-selection.md) for exact matching behavior.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Findings only, without a summary
|
|
147
|
+
pathlint check . --quiet
|
|
148
|
+
|
|
149
|
+
# Five files with the most violations
|
|
150
|
+
pathlint check . --stats
|
|
151
|
+
|
|
152
|
+
# Machine-readable report
|
|
153
|
+
pathlint check . --output-format json
|
|
154
|
+
|
|
155
|
+
# Fix preview without a summary or terminal colors
|
|
156
|
+
pathlint fix . --dry-run --quiet --no-color
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For scripting, `check` exits `0` when clean and `1` when it finds violations;
|
|
160
|
+
`fix --dry-run` exits `1` when it proposes edits. Errors exit `2`.
|
|
161
|
+
See the [CLI reference](docs/cli.md) for output formats, exit codes, and
|
|
162
|
+
migration from older commands.
|
|
163
|
+
|
|
164
|
+
## Pre-commit
|
|
165
|
+
|
|
166
|
+
Add to `.pre-commit-config.yaml`, pinning the release you want to use:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
repos:
|
|
170
|
+
- repo: https://github.com/pszemraj/pathlint
|
|
171
|
+
rev: v0.3.0
|
|
172
|
+
hooks:
|
|
173
|
+
- id: pathlint
|
|
174
|
+
args: [--exclude, vendor, --exclude, backups]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Install and run the hook:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
pre-commit install
|
|
181
|
+
pre-commit run pathlint --all-files
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The `pathlint` hook runs `pathlint check`. Use `pathlint-fix` instead to apply
|
|
185
|
+
experimental fixes, and review its changes before committing.
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
Install the development dependencies, run the tests, and check style:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pip install -e '.[dev]'
|
|
193
|
+
pytest
|
|
194
|
+
ruff check .
|
|
195
|
+
ruff format --check .
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT License - see [LICENSE.txt](LICENSE.txt).
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
Use `pathlint check` to detect usage, `pathlint fix` to apply experimental
|
|
4
|
+
migrations, and `pathlint files` to list selected files. Each command accepts
|
|
5
|
+
files or directories and defaults to the current directory. A subcommand is
|
|
6
|
+
required: bare `pathlint` prints usage and exits with status `2`.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pathlint --help
|
|
10
|
+
pathlint check --help
|
|
11
|
+
pathlint fix --help
|
|
12
|
+
pathlint files --help
|
|
13
|
+
python -m pathlint check .
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
See [file selection](file-selection.md) for exclusions and Git rules, and
|
|
17
|
+
[fixer limitations](fixer.md) before applying changes.
|
|
18
|
+
|
|
19
|
+
## Text output
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
src/utils.py:12: PL001 os.path usage: os.path.exists(value)
|
|
23
|
+
Found 1 violation in 1 of 42 files checked.
|
|
24
|
+
The experimental fixer supports some calls; other usages need manual migration.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Paths are relative to the working directory when possible, and absolute for
|
|
28
|
+
external files. Text output retains the stream's encoding and escapes characters
|
|
29
|
+
it cannot represent, including when redirected through legacy Windows encodings.
|
|
30
|
+
Each offending source line is reported once, even if it contains several usages.
|
|
31
|
+
`PL001` means "Use pathlib instead of os.path."
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Findings only; no summary
|
|
35
|
+
pathlint check . --quiet
|
|
36
|
+
|
|
37
|
+
# Show the five files with the most violations
|
|
38
|
+
pathlint check . --stats
|
|
39
|
+
|
|
40
|
+
# Optional aggressive message
|
|
41
|
+
pathlint check . --aggressive
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`check --aggressive` adds `WARNING: ARE YOU KIDDING ME? USE PATHLIB!` after the
|
|
45
|
+
summary when violations are found. It changes only the message, not detection
|
|
46
|
+
or the exit code. `--quiet` suppresses the message.
|
|
47
|
+
|
|
48
|
+
Text-mode errors go to stderr.
|
|
49
|
+
|
|
50
|
+
## JSON output
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pathlint check . --output-format json
|
|
54
|
+
pathlint check . --output-format json --stats
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For `example.py` containing:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import os
|
|
61
|
+
|
|
62
|
+
filename = os.path.join("src", "file.py")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`pathlint check example.py --output-format json` produces:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"diagnostics": [
|
|
70
|
+
{
|
|
71
|
+
"path": "example.py",
|
|
72
|
+
"line": 3,
|
|
73
|
+
"code": "PL001",
|
|
74
|
+
"message": "Use pathlib instead of os.path",
|
|
75
|
+
"source": "filename = os.path.join(\"src\", \"file.py\")"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"errors": [],
|
|
79
|
+
"summary": {
|
|
80
|
+
"files_checked": 1,
|
|
81
|
+
"files_with_issues": 1,
|
|
82
|
+
"violations": 1,
|
|
83
|
+
"errors": 0
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
JSON reports contain `diagnostics`, `errors`, and `summary`. Each diagnostic has
|
|
89
|
+
`path`, `line`, `code`, `message`, and `source`. Summary fields are
|
|
90
|
+
`files_checked`, `files_with_issues`, `violations`, and `errors`; files that fail
|
|
91
|
+
reading or parsing are not counted as checked. `--stats` adds a `statistics`
|
|
92
|
+
list containing up to five `{path, violations}` entries.
|
|
93
|
+
|
|
94
|
+
JSON is available only for `check` and cannot be combined with `--quiet` or
|
|
95
|
+
`--aggressive`. Runtime errors are included in the JSON report without mixing
|
|
96
|
+
text into stdout.
|
|
97
|
+
|
|
98
|
+
## Fix output
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Review a unified diff without changing files
|
|
102
|
+
pathlint fix src/ --dry-run
|
|
103
|
+
|
|
104
|
+
# Apply supported migrations
|
|
105
|
+
pathlint fix src/
|
|
106
|
+
|
|
107
|
+
# Print only the diff
|
|
108
|
+
pathlint fix src/ --dry-run --quiet
|
|
109
|
+
|
|
110
|
+
# Disable terminal diff colors
|
|
111
|
+
pathlint fix src/ --dry-run --no-color
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Diff colors are enabled only on interactive terminals.
|
|
115
|
+
|
|
116
|
+
The summary reminds you to run `pathlint check` for remaining usages that may
|
|
117
|
+
need manual migration. Replacement totals include import edits; they are not a
|
|
118
|
+
count of resolved `PL001` lines. `--quiet` suppresses these summaries.
|
|
119
|
+
|
|
120
|
+
## Exit codes
|
|
121
|
+
|
|
122
|
+
| Command | `0` | `1` | `2` |
|
|
123
|
+
| --- | --- | --- | --- |
|
|
124
|
+
| `check` | Clean or no files selected | Violations found | Input, discovery, read, or parse error |
|
|
125
|
+
| `fix` | Completed | - | Input, discovery, read, parse, or write error |
|
|
126
|
+
| `fix --dry-run` | No edits proposed | Edits proposed | Error |
|
|
127
|
+
| `files` | Listing completed, including empty selection | - | Input or discovery error |
|
|
128
|
+
|
|
129
|
+
Errors take precedence over violations or proposed edits. Other valid files
|
|
130
|
+
are still processed. Ctrl-C exits with status `130`.
|
|
131
|
+
|
|
132
|
+
## Migrating legacy invocations
|
|
133
|
+
|
|
134
|
+
Use explicit subcommands instead of the removed bare-path and fix-flag forms:
|
|
135
|
+
|
|
136
|
+
| Removed invocation | Replacement |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `pathlint` | `pathlint check` |
|
|
139
|
+
| `pathlint .` | `pathlint check .` |
|
|
140
|
+
| `pathlint --fix src/` | `pathlint fix src/` |
|
|
141
|
+
| `pathlint --dry-run src/` | `pathlint fix src/ --dry-run` |
|
|
142
|
+
| `python -m pathlint.linter src/` | `python -m pathlint check src/` |
|
|
143
|
+
| `python -m pathlint.autofix src/` | `python -m pathlint fix src/` |
|
|
144
|
+
|
|
145
|
+
`--stats` and `--aggressive` are check options; they are rejected by `fix`.
|
|
146
|
+
Options may appear between paths. Use `--` before filenames beginning with `-`.
|
|
147
|
+
Empty selections succeed; dry runs return `1` when they propose edits.
|
|
148
|
+
|
|
149
|
+
Return to the [README](../README.md).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Detection details
|
|
2
|
+
|
|
3
|
+
Pathlint detects `os.path` imports, aliases, function references, calls, and
|
|
4
|
+
usage in annotations:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
import os
|
|
8
|
+
import os as system
|
|
9
|
+
import os.path as osp
|
|
10
|
+
from os import path as paths
|
|
11
|
+
from os.path import exists as present
|
|
12
|
+
|
|
13
|
+
os.path.exists("file")
|
|
14
|
+
system.path.join("directory", "file")
|
|
15
|
+
osp.isdir("directory")
|
|
16
|
+
paths.dirname(__file__)
|
|
17
|
+
present("file")
|
|
18
|
+
callback = os.path.join
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Binding resolution
|
|
22
|
+
|
|
23
|
+
Pathlint resolves imported names in function, class, and comprehension scopes
|
|
24
|
+
so ordinary local shadowing is not mistaken for `os.path`. Private aliases in
|
|
25
|
+
class scopes are left unresolved because Python mangles their names. Deleted
|
|
26
|
+
class locals and cleared exception aliases fall back to module globals.
|
|
27
|
+
|
|
28
|
+
Function and generator bodies use the enclosing scope's final bindings for
|
|
29
|
+
detection; a generator's first iterable is inspected immediately. Dynamic
|
|
30
|
+
rebinding can prevent resolution. Wildcard imports invalidate tracked bindings;
|
|
31
|
+
later explicit imports can restore them.
|
|
32
|
+
|
|
33
|
+
Detection inspects `if` branches independently and uses literal conditions
|
|
34
|
+
when merging bindings in the current scope. Loop bindings are merged
|
|
35
|
+
conservatively across iterations and `else` suites. This is a static check,
|
|
36
|
+
not full Python execution analysis.
|
|
37
|
+
|
|
38
|
+
## Annotations
|
|
39
|
+
|
|
40
|
+
Expressions in annotations are checked too. Valid `os.PathLike` annotations
|
|
41
|
+
are not `os.path` usage and are not flagged. The fixer leaves annotation
|
|
42
|
+
expressions unchanged: postponed annotations and Python 3.14's lazy annotations
|
|
43
|
+
can resolve names after their bindings change.
|
|
44
|
+
|
|
45
|
+
Detection does not imply that a use can be fixed. See the
|
|
46
|
+
[fixer limitations](fixer.md) for the stricter migration rules.
|
|
47
|
+
|
|
48
|
+
Return to the [README](../README.md).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# File selection
|
|
2
|
+
|
|
3
|
+
`check`, `fix`, and `files` share the same selection rules. Use `files` to
|
|
4
|
+
inspect the selected Python files without parsing or modifying them:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pathlint files .
|
|
8
|
+
pathlint check . --exclude vendor --exclude 'training/wandb/*' --exclude backups
|
|
9
|
+
pathlint files . -e '*.generated.py' -e 'src/legacy/'
|
|
10
|
+
pathlint fix . --exclude vendor --dry-run
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Selection precedence
|
|
14
|
+
|
|
15
|
+
1. Explicit `--exclude` patterns apply to every input, including named files
|
|
16
|
+
and filenames supplied by pre-commit.
|
|
17
|
+
2. Explicitly named Python files bypass Git and built-in exclusions.
|
|
18
|
+
3. Directory scans apply built-in exclusions and, when Git is available and
|
|
19
|
+
enabled, select tracked files and untracked files that Git does not ignore.
|
|
20
|
+
|
|
21
|
+
File symlinks select their resolved Python targets once; exclusions match both
|
|
22
|
+
the link and target paths.
|
|
23
|
+
|
|
24
|
+
## Exclusion patterns
|
|
25
|
+
|
|
26
|
+
Each `-e` / `--exclude` accepts one pattern; repeat the argument for multiple
|
|
27
|
+
patterns. Explicit exclusions add to the built-in exclusions.
|
|
28
|
+
|
|
29
|
+
- Names without `/`, such as `backups` or `test_*.py`, match at any depth.
|
|
30
|
+
- Patterns containing `/` match paths relative to the working directory.
|
|
31
|
+
`./vendor` matches the top-level `vendor`, while `vendor` matches that name
|
|
32
|
+
anywhere. Absolute patterns can exclude paths outside the working directory.
|
|
33
|
+
- Directory matches exclude their descendants. A trailing `/` restricts a
|
|
34
|
+
pattern to directories.
|
|
35
|
+
- Patterns use case-sensitive `fnmatch` syntax: `*`, `?`, and bracket expressions.
|
|
36
|
+
`*` can span `/`; `**` has no additional meaning. Quote wildcards to prevent
|
|
37
|
+
shell expansion. These patterns do not support Git-style `!` negation.
|
|
38
|
+
|
|
39
|
+
Built-in directory exclusions are `__pycache__`, `.venv`, `venv`, `.git`,
|
|
40
|
+
`node_modules`, `.tox`, `build`, `dist`, `.mypy_cache`, `.pytest_cache`,
|
|
41
|
+
`.ruff_cache`, `.nox`, `.eggs`, `*.egg-info`, `site-packages`, `.idea`, and
|
|
42
|
+
`.vscode`. The `*.egg-info` pattern matches directory names such as
|
|
43
|
+
`pathlint.egg-info`; explicitly named Python files still bypass these defaults.
|
|
44
|
+
|
|
45
|
+
Disable Git ignores or built-in exclusions independently:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pathlint check . --no-gitignore
|
|
49
|
+
pathlint check . --no-default-excludes
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Git rules
|
|
53
|
+
|
|
54
|
+
In Git worktrees, directory scans include tracked files and untracked files
|
|
55
|
+
that Git does not ignore. Git handles nested `.gitignore` rules, negation,
|
|
56
|
+
repository-local excludes, and global ignores. Tracked files remain eligible
|
|
57
|
+
even when they match an ignore rule. Nonexcluded nested repositories use their
|
|
58
|
+
own Git rules.
|
|
59
|
+
|
|
60
|
+
Unreadable selected directories or Git ignore metadata produce discovery errors.
|
|
61
|
+
Warnings confined to excluded directories are suppressed. An unreadable global
|
|
62
|
+
ignore file remains an error even when stored in an excluded directory, because
|
|
63
|
+
it can affect the selected files elsewhere.
|
|
64
|
+
|
|
65
|
+
Git is optional. Without Git, directory scans use built-in and explicit
|
|
66
|
+
exclusions; `.gitignore` rules are not interpreted.
|
|
67
|
+
|
|
68
|
+
Return to the [README](../README.md).
|