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.
Files changed (38) hide show
  1. pathlint-0.3.1/PKG-INFO +230 -0
  2. pathlint-0.3.1/README.md +200 -0
  3. pathlint-0.3.1/docs/cli.md +149 -0
  4. pathlint-0.3.1/docs/detection.md +48 -0
  5. pathlint-0.3.1/docs/file-selection.md +68 -0
  6. pathlint-0.3.1/docs/fixer.md +133 -0
  7. {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/__init__.py +0 -5
  8. {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/__main__.py +1 -3
  9. pathlint-0.3.1/src/pathlint/_source.py +79 -0
  10. {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/_version.py +2 -2
  11. pathlint-0.3.1/src/pathlint/autofix.py +283 -0
  12. pathlint-0.3.1/src/pathlint/bindings.py +147 -0
  13. {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/cli.py +36 -55
  14. {pathlint-0.3.0 → pathlint-0.3.1}/src/pathlint/discovery.py +177 -103
  15. pathlint-0.3.1/src/pathlint/edits.py +261 -0
  16. pathlint-0.3.1/src/pathlint/linter.py +507 -0
  17. pathlint-0.3.1/src/pathlint/mutations.py +212 -0
  18. pathlint-0.3.1/tests/__init__.py +1 -0
  19. pathlint-0.3.1/tests/_forms.py +29 -0
  20. pathlint-0.3.1/tests/conftest.py +45 -0
  21. pathlint-0.3.1/tests/helpers.py +42 -0
  22. {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_cli.py +106 -109
  23. {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_detection.py +213 -141
  24. {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_discovery.py +177 -23
  25. {pathlint-0.3.0 → pathlint-0.3.1}/tests/test_transformations.py +216 -264
  26. pathlint-0.3.0/PKG-INFO +0 -345
  27. pathlint-0.3.0/README.md +0 -315
  28. pathlint-0.3.0/src/pathlint/autofix.py +0 -833
  29. pathlint-0.3.0/src/pathlint/linter.py +0 -789
  30. pathlint-0.3.0/tests/test_autofix.py +0 -438
  31. pathlint-0.3.0/tests/test_linter.py +0 -408
  32. {pathlint-0.3.0 → pathlint-0.3.1}/.github/workflows/publish.yml +0 -0
  33. {pathlint-0.3.0 → pathlint-0.3.1}/.github/workflows/test.yml +0 -0
  34. {pathlint-0.3.0 → pathlint-0.3.1}/.gitignore +0 -0
  35. {pathlint-0.3.0 → pathlint-0.3.1}/.pre-commit-config.yaml +0 -0
  36. {pathlint-0.3.0 → pathlint-0.3.1}/.pre-commit-hooks.yaml +0 -0
  37. {pathlint-0.3.0 → pathlint-0.3.1}/LICENSE.txt +0 -0
  38. {pathlint-0.3.0 → pathlint-0.3.1}/pyproject.toml +0 -0
@@ -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).
@@ -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).