Compare commits
75 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7987951e24 | ||
![]() |
e5e5dad792 | ||
![]() |
24e4cb20ab | ||
![]() |
e7bf7b4619 | ||
![]() |
71e380aedf | ||
![]() |
2630801f95 | ||
![]() |
b0f36f5b42 | ||
![]() |
314f8cf92b | ||
![]() |
d0ff3bd6cb | ||
![]() |
a41dc89f1f | ||
![]() |
950ec38c11 | ||
![]() |
2c135edf37 | ||
![]() |
6144c46c6a | ||
![]() |
dd278cb316 | ||
![]() |
dbb14eac93 | ||
![]() |
5342d2eeda | ||
![]() |
9f38928414 | ||
![]() |
3e9dd25dad | ||
![]() |
bb802cf19a | ||
![]() |
5ae38dd370 | ||
![]() |
45cbe572ee | ||
![]() |
fccd70cff1 | ||
![]() |
00c0d6d91a | ||
![]() |
0580ecbef3 | ||
![]() |
ed64d89faa | ||
![]() |
452d3b68f4 | ||
![]() |
256f3420b1 | ||
![]() |
00cb6d15c5 | ||
![]() |
14e1de805a | ||
![]() |
5f23701708 | ||
![]() |
9c129567e7 | ||
![]() |
c02ca47daa | ||
![]() |
edaf085a18 | ||
![]() |
b844c8a136 | ||
![]() |
d82da0f0e9 | ||
![]() |
8a737e727a | ||
![]() |
d330deea00 | ||
![]() |
3d8129001f | ||
![]() |
459562c71a | ||
![]() |
99dbf3006b | ||
![]() |
c0b92f3888 | ||
![]() |
e58baf15b9 | ||
![]() |
1455ae4731 | ||
![]() |
584d0331c8 | ||
![]() |
6e9654065c | ||
![]() |
8dc912774e | ||
![]() |
40b73f2fb5 | ||
![]() |
e157ba4de5 | ||
![]() |
fdabd424e2 | ||
![]() |
9431e98522 | ||
![]() |
3b00112ac5 | ||
![]() |
0aabac4fe0 | ||
![]() |
ed33205579 | ||
![]() |
6000d37f09 | ||
![]() |
30759ca782 | ||
![]() |
84ac1a947d | ||
![]() |
0db1173bbc | ||
![]() |
3fab5ade71 | ||
![]() |
e54f86bae4 | ||
![]() |
96ca1b6be3 | ||
![]() |
17efac45f9 | ||
![]() |
73f651f02f | ||
![]() |
f6c7c98f34 | ||
![]() |
d670b0439c | ||
![]() |
56896264e4 | ||
![]() |
efd9778873 | ||
![]() |
c472557ba8 | ||
![]() |
53a219056d | ||
![]() |
c98fc0c128 | ||
![]() |
f54f34799b | ||
![]() |
484a669699 | ||
![]() |
fff747d61b | ||
![]() |
9995bffbe4 | ||
![]() |
7452902c77 | ||
![]() |
32ebb93003 |
@ -1,4 +1,3 @@
|
||||
node: $Format:%H$
|
||||
node-date: $Format:%cI$
|
||||
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
|
||||
ref-names: $Format:%D$
|
||||
describe-name: $Format:%(describe:tags=true,match=[0-9]*)$
|
||||
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +1,2 @@
|
||||
.git_archival.txt export-subst
|
||||
*.py diff=python
|
||||
|
22
.github/workflows/diff_shades.yml
vendored
22
.github/workflows/diff_shades.yml
vendored
@ -34,7 +34,8 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: >
|
||||
python scripts/diff_shades_gha_helper.py config ${{ github.event_name }} ${{ matrix.mode }}
|
||||
python scripts/diff_shades_gha_helper.py config ${{ github.event_name }}
|
||||
${{ matrix.mode }}
|
||||
|
||||
analysis:
|
||||
name: analysis / ${{ matrix.mode }}
|
||||
@ -44,11 +45,11 @@ jobs:
|
||||
HATCH_BUILD_HOOKS_ENABLE: "1"
|
||||
# Clang is less picky with the C code it's given than gcc (and may
|
||||
# generate faster binaries too).
|
||||
CC: clang-14
|
||||
CC: clang-18
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.configure.outputs.matrix )}}
|
||||
include: ${{ fromJson(needs.configure.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout this repository (full clone)
|
||||
@ -110,19 +111,19 @@ jobs:
|
||||
${{ matrix.baseline-analysis }} ${{ matrix.target-analysis }}
|
||||
|
||||
- name: Upload diff report
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.mode }}-diff.html
|
||||
path: diff.html
|
||||
|
||||
- name: Upload baseline analysis
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.baseline-analysis }}
|
||||
path: ${{ matrix.baseline-analysis }}
|
||||
|
||||
- name: Upload target analysis
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target-analysis }}
|
||||
path: ${{ matrix.target-analysis }}
|
||||
@ -130,14 +131,13 @@ jobs:
|
||||
- name: Generate summary file (PR only)
|
||||
if: github.event_name == 'pull_request' && matrix.mode == 'preview-changes'
|
||||
run: >
|
||||
python helper.py comment-body
|
||||
${{ matrix.baseline-analysis }} ${{ matrix.target-analysis }}
|
||||
${{ matrix.baseline-sha }} ${{ matrix.target-sha }}
|
||||
${{ github.event.pull_request.number }}
|
||||
python helper.py comment-body ${{ matrix.baseline-analysis }}
|
||||
${{ matrix.target-analysis }} ${{ matrix.baseline-sha }}
|
||||
${{ matrix.target-sha }} ${{ github.event.pull_request.number }}
|
||||
|
||||
- name: Upload summary file (PR only)
|
||||
if: github.event_name == 'pull_request' && matrix.mode == 'preview-changes'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: .pr-comment.json
|
||||
path: .pr-comment.json
|
||||
|
10
.github/workflows/pypi_upload.yml
vendored
10
.github/workflows/pypi_upload.yml
vendored
@ -50,8 +50,8 @@ jobs:
|
||||
# Keep cibuildwheel version in sync with below
|
||||
- name: Install cibuildwheel and pypyp
|
||||
run: |
|
||||
pipx install cibuildwheel==2.21.2
|
||||
pipx install pypyp==1
|
||||
pipx install cibuildwheel==2.22.0
|
||||
pipx install pypyp==1.3.0
|
||||
- name: generate matrix
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
@ -92,14 +92,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Keep cibuildwheel version in sync with above
|
||||
- uses: pypa/cibuildwheel@v2.21.2
|
||||
- uses: pypa/cibuildwheel@v2.23.3
|
||||
with:
|
||||
only: ${{ matrix.only }}
|
||||
|
||||
- name: Upload wheels as workflow artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}-mypyc-wheels
|
||||
name: ${{ matrix.only }}-mypyc-wheels
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
- if: github.event_name == 'release'
|
||||
|
4
.github/workflows/upload_binary.yml
vendored
4
.github/workflows/upload_binary.yml
vendored
@ -13,13 +13,13 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2019, ubuntu-20.04, macos-latest]
|
||||
os: [windows-2019, ubuntu-22.04, macos-latest]
|
||||
include:
|
||||
- os: windows-2019
|
||||
pathsep: ";"
|
||||
asset_name: black_windows.exe
|
||||
executable_mime: "application/vnd.microsoft.portable-executable"
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04
|
||||
pathsep: ":"
|
||||
asset_name: black_linux
|
||||
executable_mime: "application/x-executable"
|
||||
|
@ -24,12 +24,12 @@ repos:
|
||||
additional_dependencies: *version_check_dependencies
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
rev: 6.0.1
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 7.1.0
|
||||
rev: 7.2.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
@ -39,17 +39,21 @@ repos:
|
||||
exclude: ^src/blib2to3/
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.11.2
|
||||
rev: v1.15.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
exclude: ^(docs/conf.py|scripts/generate_schema.py)$
|
||||
args: []
|
||||
additional_dependencies: &mypy_deps
|
||||
- types-PyYAML
|
||||
- types-atheris
|
||||
- tomli >= 0.2.6, < 2.0.0
|
||||
- click >= 8.1.0, != 8.1.4, != 8.1.5
|
||||
- click >= 8.2.0
|
||||
# Click is intentionally out-of-sync with pyproject.toml
|
||||
# v8.2 has breaking changes. We work around them at runtime, but we need the newer stubs.
|
||||
- packaging >= 22.0
|
||||
- platformdirs >= 2.1.0
|
||||
- pytokens >= 0.1.10
|
||||
- pytest
|
||||
- hypothesis
|
||||
- aiohttp >= 3.7.4
|
||||
@ -62,14 +66,15 @@ repos:
|
||||
args: ["--python-version=3.10"]
|
||||
additional_dependencies: *mypy_deps
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v4.0.0-alpha.8
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.5.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [markdown, yaml, json]
|
||||
exclude: \.github/workflows/diff_shades\.yml
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.6.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
@ -16,3 +16,6 @@ python:
|
||||
path: .
|
||||
extra_requirements:
|
||||
- d
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
123
CHANGES.md
123
CHANGES.md
@ -1,5 +1,128 @@
|
||||
# Change Log
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Highlights
|
||||
|
||||
<!-- Include any especially major or disruptive changes here -->
|
||||
|
||||
### Stable style
|
||||
|
||||
<!-- Changes that affect Black's stable style -->
|
||||
|
||||
- Fix crash while formatting a long `del` statement containing tuples (#4628)
|
||||
- Fix crash while formatting expressions using the walrus operator in complex `with`
|
||||
statements (#4630)
|
||||
- Handle `# fmt: skip` followed by a comment at the end of file (#4635)
|
||||
- Fix crash when a tuple appears in the `as` clause of a `with` statement (#4634)
|
||||
- Fix crash when tuple is used as a context manager inside a `with` statement (#4646)
|
||||
- Fix crash on a `\\r\n` (#4673)
|
||||
- Fix crash on `await ...` (where `...` is a literal `Ellipsis`) (#4676)
|
||||
- Remove support for pre-python 3.7 `await/async` as soft keywords/variable names
|
||||
(#4676)
|
||||
|
||||
### Preview style
|
||||
|
||||
<!-- Changes that affect Black's preview style -->
|
||||
|
||||
- Fix a bug where one-liner functions/conditionals marked with `# fmt: skip` would still
|
||||
be formatted (#4552)
|
||||
|
||||
### Configuration
|
||||
|
||||
<!-- Changes to how Black can be configured -->
|
||||
|
||||
### Packaging
|
||||
|
||||
<!-- Changes to how Black is packaged, such as dependency requirements -->
|
||||
|
||||
### Parser
|
||||
|
||||
<!-- Changes to the parser or to version autodetection -->
|
||||
|
||||
- Rewrite tokenizer to improve performance and compliance (#4536)
|
||||
- Fix bug where certain unusual expressions (e.g., lambdas) were not accepted in type
|
||||
parameter bounds and defaults. (#4602)
|
||||
|
||||
### Performance
|
||||
|
||||
<!-- Changes that improve Black's performance. -->
|
||||
|
||||
### Output
|
||||
|
||||
<!-- Changes to Black's terminal output and error messages -->
|
||||
|
||||
### _Blackd_
|
||||
|
||||
<!-- Changes to blackd -->
|
||||
|
||||
### Integrations
|
||||
|
||||
<!-- For example, Docker, GitHub Actions, pre-commit, editors -->
|
||||
|
||||
- Fix the version check in the vim file to reject Python 3.8 (#4567)
|
||||
- Enhance GitHub Action `psf/black` to read Black version from an additional section in
|
||||
pyproject.toml: `[project.dependency-groups]` (#4606)
|
||||
|
||||
### Documentation
|
||||
|
||||
<!-- Major changes to documentation and policies. Small docs changes
|
||||
don't need a changelog entry. -->
|
||||
|
||||
## 25.1.0
|
||||
|
||||
### Highlights
|
||||
|
||||
This release introduces the new 2025 stable style (#4558), stabilizing the following
|
||||
changes:
|
||||
|
||||
- Normalize casing of Unicode escape characters in strings to lowercase (#2916)
|
||||
- Fix inconsistencies in whether certain strings are detected as docstrings (#4095)
|
||||
- Consistently add trailing commas to typed function parameters (#4164)
|
||||
- Remove redundant parentheses in if guards for case blocks (#4214)
|
||||
- Add parentheses to if clauses in case blocks when the line is too long (#4269)
|
||||
- Whitespace before `# fmt: skip` comments is no longer normalized (#4146)
|
||||
- Fix line length computation for certain expressions that involve the power operator
|
||||
(#4154)
|
||||
- Check if there is a newline before the terminating quotes of a docstring (#4185)
|
||||
- Fix type annotation spacing between `*` and more complex type variable tuple (#4440)
|
||||
|
||||
The following changes were not in any previous release:
|
||||
|
||||
- Remove parentheses around sole list items (#4312)
|
||||
- Generic function definitions are now formatted more elegantly: parameters are split
|
||||
over multiple lines first instead of type parameter definitions (#4553)
|
||||
|
||||
### Stable style
|
||||
|
||||
- Fix formatting cells in IPython notebooks with magic methods and starting or trailing
|
||||
empty lines (#4484)
|
||||
- Fix crash when formatting `with` statements containing tuple generators/unpacking
|
||||
(#4538)
|
||||
|
||||
### Preview style
|
||||
|
||||
- Fix/remove string merging changing f-string quotes on f-strings with internal quotes
|
||||
(#4498)
|
||||
- Collapse multiple empty lines after an import into one (#4489)
|
||||
- Prevent `string_processing` and `wrap_long_dict_values_in_parens` from removing
|
||||
parentheses around long dictionary values (#4377)
|
||||
- Move `wrap_long_dict_values_in_parens` from the unstable to preview style (#4561)
|
||||
|
||||
### Packaging
|
||||
|
||||
- Store license identifier inside the `License-Expression` metadata field, see
|
||||
[PEP 639](https://peps.python.org/pep-0639/). (#4479)
|
||||
|
||||
### Performance
|
||||
|
||||
- Speed up the `is_fstring_start` function in Black's tokenizer (#4541)
|
||||
|
||||
### Integrations
|
||||
|
||||
- If using stdin with `--stdin-filename` set to a force excluded path, stdin won't be
|
||||
formatted. (#4539)
|
||||
|
||||
## 24.10.0
|
||||
|
||||
### Highlights
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Contributing to _Black_
|
||||
|
||||
Welcome! Happy to see you willing to make the project better. Have you read the entire
|
||||
[user documentation](https://black.readthedocs.io/en/latest/) yet?
|
||||
Welcome future contributor! We're happy to see you willing to make the project better.
|
||||
|
||||
Our [contributing documentation](https://black.readthedocs.org/en/latest/contributing/)
|
||||
contains details on all you need to know about contributing to _Black_, the basics to
|
||||
the internals of _Black_.
|
||||
If you aren't familiar with _Black_, or are looking for documentation on something
|
||||
specific, the [user documentation](https://black.readthedocs.io/en/latest/) is the best
|
||||
place to look.
|
||||
|
||||
We look forward to your contributions!
|
||||
For getting started on contributing, please read the
|
||||
[contributing documentation](https://black.readthedocs.org/en/latest/contributing/) for
|
||||
all you need to know.
|
||||
|
||||
Thank you, and we look forward to your contributions!
|
||||
|
@ -38,7 +38,7 @@ Try it out now using the [Black Playground](https://black.vercel.app). Watch the
|
||||
|
||||
### Installation
|
||||
|
||||
_Black_ can be installed by running `pip install black`. It requires Python 3.8+ to run.
|
||||
_Black_ can be installed by running `pip install black`. It requires Python 3.9+ to run.
|
||||
If you want to format Jupyter Notebooks, install with `pip install "black[jupyter]"`.
|
||||
|
||||
If you can't wait for the latest _hotness_ and want to install from GitHub, use:
|
||||
@ -137,8 +137,8 @@ SQLAlchemy, Poetry, PyPA applications (Warehouse, Bandersnatch, Pipenv, virtuale
|
||||
pandas, Pillow, Twisted, LocalStack, every Datadog Agent Integration, Home Assistant,
|
||||
Zulip, Kedro, OpenOA, FLORIS, ORBIT, WOMBAT, and many more.
|
||||
|
||||
The following organizations use _Black_: Facebook, Dropbox, KeepTruckin, Lyft, Mozilla,
|
||||
Quora, Duolingo, QuantumBlack, Tesla, Archer Aviation.
|
||||
The following organizations use _Black_: Dropbox, KeepTruckin, Lyft, Mozilla, Quora,
|
||||
Duolingo, QuantumBlack, Tesla, Archer Aviation.
|
||||
|
||||
Are we missing anyone? Let us know.
|
||||
|
||||
|
@ -71,6 +71,7 @@ def read_version_specifier_from_pyproject() -> str:
|
||||
return f"=={version}"
|
||||
|
||||
arrays = [
|
||||
*pyproject.get("dependency-groups", {}).values(),
|
||||
pyproject.get("project", {}).get("dependencies"),
|
||||
*pyproject.get("project", {}).get("optional-dependencies", {}).values(),
|
||||
]
|
||||
|
@ -75,8 +75,8 @@ def _initialize_black_env(upgrade=False):
|
||||
return True
|
||||
|
||||
pyver = sys.version_info[:3]
|
||||
if pyver < (3, 8):
|
||||
print("Sorry, Black requires Python 3.8+ to run.")
|
||||
if pyver < (3, 9):
|
||||
print("Sorry, Black requires Python 3.9+ to run.")
|
||||
return False
|
||||
|
||||
from pathlib import Path
|
||||
|
@ -29,8 +29,8 @@ frequently than monthly nets rapidly diminishing returns.
|
||||
**You must have `write` permissions for the _Black_ repository to cut a release.**
|
||||
|
||||
The 10,000 foot view of the release process is that you prepare a release PR and then
|
||||
publish a [GitHub Release]. This triggers [release automation](#release-workflows) that builds
|
||||
all release artifacts and publishes them to the various platforms we publish to.
|
||||
publish a [GitHub Release]. This triggers [release automation](#release-workflows) that
|
||||
builds all release artifacts and publishes them to the various platforms we publish to.
|
||||
|
||||
We now have a `scripts/release.py` script to help with cutting the release PRs.
|
||||
|
||||
@ -96,8 +96,9 @@ In the end, use your best judgement and ask other maintainers for their thoughts
|
||||
|
||||
## Release workflows
|
||||
|
||||
All of _Black_'s release automation uses [GitHub Actions]. All workflows are therefore configured
|
||||
using YAML files in the `.github/workflows` directory of the _Black_ repository.
|
||||
All of _Black_'s release automation uses [GitHub Actions]. All workflows are therefore
|
||||
configured using YAML files in the `.github/workflows` directory of the _Black_
|
||||
repository.
|
||||
|
||||
They are triggered by the publication of a [GitHub Release].
|
||||
|
||||
|
@ -7,7 +7,14 @@ An overview on contributing to the _Black_ project.
|
||||
Development on the latest version of Python is preferred. You can use any operating
|
||||
system.
|
||||
|
||||
Install development dependencies inside a virtual environment of your choice, for
|
||||
First clone the _Black_ repository:
|
||||
|
||||
```console
|
||||
$ git clone https://github.com/psf/black.git
|
||||
$ cd black
|
||||
```
|
||||
|
||||
Then install development dependencies inside a virtual environment of your choice, for
|
||||
example:
|
||||
|
||||
```console
|
||||
@ -48,13 +55,16 @@ Further examples of invoking the tests
|
||||
# Run tests on a specific python version
|
||||
(.venv)$ tox -e py39
|
||||
|
||||
# pass arguments to pytest
|
||||
# Run an individual test
|
||||
(.venv)$ pytest -k <test name>
|
||||
|
||||
# Pass arguments to pytest
|
||||
(.venv)$ tox -e py -- --no-cov
|
||||
|
||||
# print full tree diff, see documentation below
|
||||
# Print full tree diff, see documentation below
|
||||
(.venv)$ tox -e py -- --print-full-tree
|
||||
|
||||
# disable diff printing, see documentation below
|
||||
# Disable diff printing, see documentation below
|
||||
(.venv)$ tox -e py -- --print-tree-diff=False
|
||||
```
|
||||
|
||||
@ -99,16 +109,22 @@ default. To turn it off pass `--print-tree-diff=False`.
|
||||
`Black` has CI that will check for an entry corresponding to your PR in `CHANGES.md`. If
|
||||
you feel this PR does not require a changelog entry please state that in a comment and a
|
||||
maintainer can add a `skip news` label to make the CI pass. Otherwise, please ensure you
|
||||
have a line in the following format:
|
||||
have a line in the following format added below the appropriate header:
|
||||
|
||||
```md
|
||||
- `Black` is now more awesome (#X)
|
||||
```
|
||||
|
||||
<!---
|
||||
The Next PR Number link uses HTML because of a bug in MyST-Parser that double-escapes the ampersand, causing the query parameters to not be processed.
|
||||
MyST-Parser issue: https://github.com/executablebooks/MyST-Parser/issues/760
|
||||
MyST-Parser stalled fix PR: https://github.com/executablebooks/MyST-Parser/pull/929
|
||||
-->
|
||||
|
||||
Note that X should be your PR number, not issue number! To workout X, please use
|
||||
[Next PR Number](https://ichard26.github.io/next-pr-number/?owner=psf&name=black). This
|
||||
is not perfect but saves a lot of release overhead as now the releaser does not need to
|
||||
go back and workout what to add to the `CHANGES.md` for each release.
|
||||
<a href="https://ichard26.github.io/next-pr-number/?owner=psf&name=black">Next PR
|
||||
Number</a>. This is not perfect but saves a lot of release overhead as now the releaser
|
||||
does not need to go back and workout what to add to the `CHANGES.md` for each release.
|
||||
|
||||
### Style Changes
|
||||
|
||||
@ -116,7 +132,7 @@ If a change would affect the advertised code style, please modify the documentat
|
||||
_Black_ code style) to reflect that change. Patches that fix unintended bugs in
|
||||
formatting don't need to be mentioned separately though. If the change is implemented
|
||||
with the `--preview` flag, please include the change in the future style document
|
||||
instead and write the changelog entry under a dedicated "Preview changes" heading.
|
||||
instead and write the changelog entry under the dedicated "Preview style" heading.
|
||||
|
||||
### Docs Testing
|
||||
|
||||
@ -124,17 +140,17 @@ If you make changes to docs, you can test they still build locally too.
|
||||
|
||||
```console
|
||||
(.venv)$ pip install -r docs/requirements.txt
|
||||
(.venv)$ pip install -e .[d]
|
||||
(.venv)$ pip install -e ".[d]"
|
||||
(.venv)$ sphinx-build -a -b html -W docs/ docs/_build/
|
||||
```
|
||||
|
||||
## Hygiene
|
||||
|
||||
If you're fixing a bug, add a test. Run it first to confirm it fails, then fix the bug,
|
||||
run it again to confirm it's really fixed.
|
||||
and run the test again to confirm it's really fixed.
|
||||
|
||||
If adding a new feature, add a test. In fact, always add a test. But wait, before adding
|
||||
any large feature, first open an issue for us to discuss the idea first.
|
||||
If adding a new feature, add a test. In fact, always add a test. If adding a large
|
||||
feature, please first open an issue to discuss it beforehand.
|
||||
|
||||
## Finally
|
||||
|
||||
|
13
docs/faq.md
13
docs/faq.md
@ -84,16 +84,19 @@ See [Using _Black_ with other tools](labels/why-pycodestyle-warnings).
|
||||
|
||||
## Which Python versions does Black support?
|
||||
|
||||
Currently the runtime requires Python 3.8-3.11. Formatting is supported for files
|
||||
containing syntax from Python 3.3 to 3.11. We promise to support at least all Python
|
||||
versions that have not reached their end of life. This is the case for both running
|
||||
_Black_ and formatting code.
|
||||
_Black_ generally supports all Python versions supported by CPython (see
|
||||
[the Python devguide](https://devguide.python.org/versions/) for current information).
|
||||
We promise to support at least all Python versions that have not reached their end of
|
||||
life. This is the case for both running _Black_ and formatting code.
|
||||
|
||||
Support for formatting Python 2 code was removed in version 22.0. While we've made no
|
||||
plans to stop supporting older Python 3 minor versions immediately, their support might
|
||||
also be removed some time in the future without a deprecation period.
|
||||
|
||||
Runtime support for 3.7 was removed in version 23.7.0.
|
||||
`await`/`async` as soft keywords/indentifiers are no longer supported as of 25.2.0.
|
||||
|
||||
Runtime support for 3.6 was removed in version 22.10.0, for 3.7 in version 23.7.0, and
|
||||
for 3.8 in version 24.10.0.
|
||||
|
||||
## Why does my linter or typechecker complain after I format my code?
|
||||
|
||||
|
@ -16,7 +16,7 @@ Also, you can try out _Black_ online for minimal fuss on the
|
||||
|
||||
## Installation
|
||||
|
||||
_Black_ can be installed by running `pip install black`. It requires Python 3.8+ to run.
|
||||
_Black_ can be installed by running `pip install black`. It requires Python 3.9+ to run.
|
||||
If you want to format Jupyter Notebooks, install with `pip install "black[jupyter]"`.
|
||||
|
||||
If you use pipx, you can install Black with `pipx install black`.
|
||||
|
@ -236,7 +236,7 @@ Configuration:
|
||||
|
||||
#### Installation
|
||||
|
||||
This plugin **requires Vim 7.0+ built with Python 3.8+ support**. It needs Python 3.8 to
|
||||
This plugin **requires Vim 7.0+ built with Python 3.9+ support**. It needs Python 3.9 to
|
||||
be able to run _Black_ inside the Vim process which is much faster than calling an
|
||||
external command.
|
||||
|
||||
|
@ -37,10 +37,10 @@ the `pyproject.toml` file. `version` can be any
|
||||
[valid version specifier](https://packaging.python.org/en/latest/glossary/#term-Version-Specifier)
|
||||
or just the version number if you want an exact version. To read the version from the
|
||||
`pyproject.toml` file instead, set `use_pyproject` to `true`. This will first look into
|
||||
the `tool.black.required-version` field, then the `project.dependencies` array and
|
||||
finally the `project.optional-dependencies` table. The action defaults to the latest
|
||||
release available on PyPI. Only versions available from PyPI are supported, so no commit
|
||||
SHAs or branch names.
|
||||
the `tool.black.required-version` field, then the `dependency-groups` table, then the
|
||||
`project.dependencies` array and finally the `project.optional-dependencies` table. The
|
||||
action defaults to the latest release available on PyPI. Only versions available from
|
||||
PyPI are supported, so no commit SHAs or branch names.
|
||||
|
||||
If you want to include Jupyter Notebooks, _Black_ must be installed with the `jupyter`
|
||||
extra. Installing the extra and including Jupyter Notebook files can be configured via
|
||||
@ -74,9 +74,14 @@ If you want to match versions covered by Black's
|
||||
version: "~= 22.0"
|
||||
```
|
||||
|
||||
If you want to read the version from `pyproject.toml`, set `use_pyproject` to `true`:
|
||||
If you want to read the version from `pyproject.toml`, set `use_pyproject` to `true`.
|
||||
Note that this requires Python >= 3.11, so using the setup-python action may be
|
||||
required, for example:
|
||||
|
||||
```yaml
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
options: "--check --verbose"
|
||||
|
@ -8,7 +8,7 @@ Use [pre-commit](https://pre-commit.com/). Once you
|
||||
repos:
|
||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 24.10.0
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
# It is recommended to specify the latest version of Python
|
||||
@ -35,7 +35,7 @@ include Jupyter Notebooks. To use this hook, simply replace the hook's `id: blac
|
||||
repos:
|
||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 24.10.0
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black-jupyter
|
||||
# It is recommended to specify the latest version of Python
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Used by ReadTheDocs; pinned requirements for stability.
|
||||
|
||||
myst-parser==3.0.1
|
||||
Sphinx==7.4.7
|
||||
myst-parser==4.0.1
|
||||
Sphinx==8.2.3
|
||||
# Older versions break Sphinx even though they're declared to be supported.
|
||||
docutils==0.20.1
|
||||
sphinxcontrib-programoutput==0.17
|
||||
docutils==0.21.2
|
||||
sphinxcontrib-programoutput==0.18
|
||||
sphinx_copybutton==0.5.2
|
||||
furo==2024.8.6
|
||||
|
@ -250,6 +250,11 @@ exception of [capital "R" prefixes](#rstrings-and-rstrings), unicode literal mar
|
||||
(`u`) are removed because they are meaningless in Python 3, and in the case of multiple
|
||||
characters "r" is put first as in spoken language: "raw f-string".
|
||||
|
||||
Another area where Python allows multiple ways to format a string is escape sequences.
|
||||
For example, `"\uabcd"` and `"\uABCD"` evaluate to the same string. _Black_ normalizes
|
||||
such escape sequences to lowercase, but uses uppercase for `\N` named character escapes,
|
||||
such as `"\N{MEETEI MAYEK LETTER HUK}"`.
|
||||
|
||||
The main reason to standardize on a single form of quotes is aesthetics. Having one kind
|
||||
of quotes everywhere reduces reader distraction. It will also enable a future version of
|
||||
_Black_ to merge consecutive string literals that ended up on the same line (see
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
## Preview style
|
||||
|
||||
(labels/preview-style)=
|
||||
|
||||
Experimental, potentially disruptive style changes are gathered under the `--preview`
|
||||
CLI flag. At the end of each year, these changes may be adopted into the default style,
|
||||
as described in [The Black Code Style](index.md). Because the functionality is
|
||||
@ -20,24 +22,13 @@ demoted from the `--preview` to the `--unstable` style, users can use the
|
||||
|
||||
Currently, the following features are included in the preview style:
|
||||
|
||||
- `hex_codes_in_unicode_sequences`: normalize casing of Unicode escape characters in
|
||||
strings
|
||||
- `unify_docstring_detection`: fix inconsistencies in whether certain strings are
|
||||
detected as docstrings
|
||||
- `no_normalize_fmt_skip_whitespace`: whitespace before `# fmt: skip` comments is no
|
||||
longer normalized
|
||||
- `typed_params_trailing_comma`: consistently add trailing commas to typed function
|
||||
parameters
|
||||
- `is_simple_lookup_for_doublestar_expression`: fix line length computation for certain
|
||||
expressions that involve the power operator
|
||||
- `docstring_check_for_newline`: checks if there is a newline before the terminating
|
||||
quotes of a docstring
|
||||
- `remove_redundant_guard_parens`: Removes redundant parentheses in `if` guards for
|
||||
`case` blocks.
|
||||
- `parens_for_long_if_clauses_in_case_block`: Adds parentheses to `if` clauses in `case`
|
||||
blocks when the line is too long
|
||||
- `pep646_typed_star_arg_type_var_tuple`: fix type annotation spacing between * and more
|
||||
complex type variable tuple (i.e. `def fn(*args: *tuple[*Ts, T]) -> None: pass`)
|
||||
- `always_one_newline_after_import`: Always force one blank line after import
|
||||
statements, except when the line after the import is a comment or an import statement
|
||||
- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries
|
||||
([see below](labels/wrap-long-dict-values))
|
||||
- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behaviour on one-liner declarations,
|
||||
such as `def foo(): return "mock" # fmt: skip`, where previously the declaration
|
||||
would have been incorrectly collapsed.
|
||||
|
||||
(labels/unstable-features)=
|
||||
|
||||
@ -45,13 +36,38 @@ The unstable style additionally includes the following features:
|
||||
|
||||
- `string_processing`: split long string literals and related changes
|
||||
([see below](labels/string-processing))
|
||||
- `wrap_long_dict_values_in_parens`: add parentheses to long values in dictionaries
|
||||
([see below](labels/wrap-long-dict-values))
|
||||
- `multiline_string_handling`: more compact formatting of expressions involving
|
||||
multiline strings ([see below](labels/multiline-string-handling))
|
||||
- `hug_parens_with_braces_and_square_brackets`: more compact formatting of nested
|
||||
brackets ([see below](labels/hug-parens))
|
||||
|
||||
(labels/wrap-long-dict-values)=
|
||||
|
||||
### Improved parentheses management in dicts
|
||||
|
||||
For dict literals with long values, they are now wrapped in parentheses. Unnecessary
|
||||
parentheses are now removed. For example:
|
||||
|
||||
```python
|
||||
my_dict = {
|
||||
"a key in my dict": a_very_long_variable
|
||||
* and_a_very_long_function_call()
|
||||
/ 100000.0,
|
||||
"another key": (short_value),
|
||||
}
|
||||
```
|
||||
|
||||
will be changed to:
|
||||
|
||||
```python
|
||||
my_dict = {
|
||||
"a key in my dict": (
|
||||
a_very_long_variable * and_a_very_long_function_call() / 100000.0
|
||||
),
|
||||
"another key": short_value,
|
||||
}
|
||||
```
|
||||
|
||||
(labels/hug-parens)=
|
||||
|
||||
### Improved multiline dictionary and list indentation for sole function parameter
|
||||
@ -132,37 +148,11 @@ foo(
|
||||
|
||||
_Black_ will split long string literals and merge short ones. Parentheses are used where
|
||||
appropriate. When split, parts of f-strings that don't need formatting are converted to
|
||||
plain strings. User-made splits are respected when they do not exceed the line length
|
||||
limit. Line continuation backslashes are converted into parenthesized strings.
|
||||
Unnecessary parentheses are stripped. The stability and status of this feature is
|
||||
tracked in [this issue](https://github.com/psf/black/issues/2188).
|
||||
|
||||
(labels/wrap-long-dict-values)=
|
||||
|
||||
### Improved parentheses management in dicts
|
||||
|
||||
For dict literals with long values, they are now wrapped in parentheses. Unnecessary
|
||||
parentheses are now removed. For example:
|
||||
|
||||
```python
|
||||
my_dict = {
|
||||
"a key in my dict": a_very_long_variable
|
||||
* and_a_very_long_function_call()
|
||||
/ 100000.0,
|
||||
"another key": (short_value),
|
||||
}
|
||||
```
|
||||
|
||||
will be changed to:
|
||||
|
||||
```python
|
||||
my_dict = {
|
||||
"a key in my dict": (
|
||||
a_very_long_variable * and_a_very_long_function_call() / 100000.0
|
||||
),
|
||||
"another key": short_value,
|
||||
}
|
||||
```
|
||||
plain strings. f-strings will not be merged if they contain internal quotes and it would
|
||||
change their quotation mark style. User-made splits are respected when they do not
|
||||
exceed the line length limit. Line continuation backslashes are converted into
|
||||
parenthesized strings. Unnecessary parentheses are stripped. The stability and status of
|
||||
this feature istracked in [this issue](https://github.com/psf/black/issues/2188).
|
||||
|
||||
(labels/multiline-string-handling)=
|
||||
|
||||
@ -277,52 +267,3 @@ s = ( # Top comment
|
||||
# Bottom comment
|
||||
)
|
||||
```
|
||||
|
||||
## Potential future changes
|
||||
|
||||
This section lists changes that we may want to make in the future, but that aren't
|
||||
implemented yet.
|
||||
|
||||
### Using backslashes for with statements
|
||||
|
||||
[Backslashes are bad and should be never be used](labels/why-no-backslashes) however
|
||||
there is one exception: `with` statements using multiple context managers. Before Python
|
||||
3.9 Python's grammar does not allow organizing parentheses around the series of context
|
||||
managers.
|
||||
|
||||
We don't want formatting like:
|
||||
|
||||
```py3
|
||||
with make_context_manager1() as cm1, make_context_manager2() as cm2, make_context_manager3() as cm3, make_context_manager4() as cm4:
|
||||
... # nothing to split on - line too long
|
||||
```
|
||||
|
||||
So _Black_ will, when we implement this, format it like this:
|
||||
|
||||
```py3
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
make_context_manager3() as cm3, \
|
||||
make_context_manager4() as cm4 \
|
||||
:
|
||||
... # backslashes and an ugly stranded colon
|
||||
```
|
||||
|
||||
Although when the target version is Python 3.9 or higher, _Black_ uses parentheses
|
||||
instead in `--preview` mode (see below) since they're allowed in Python 3.9 and higher.
|
||||
|
||||
An alternative to consider if the backslashes in the above formatting are undesirable is
|
||||
to use {external:py:obj}`contextlib.ExitStack` to combine context managers in the
|
||||
following way:
|
||||
|
||||
```python
|
||||
with contextlib.ExitStack() as exit_stack:
|
||||
cm1 = exit_stack.enter_context(make_context_manager1())
|
||||
cm2 = exit_stack.enter_context(make_context_manager2())
|
||||
cm3 = exit_stack.enter_context(make_context_manager3())
|
||||
cm4 = exit_stack.enter_context(make_context_manager4())
|
||||
...
|
||||
```
|
||||
|
||||
(labels/preview-style)=
|
||||
|
@ -70,17 +70,17 @@ See also [the style documentation](labels/line-length).
|
||||
|
||||
Python versions that should be supported by Black's output. You can run `black --help`
|
||||
and look for the `--target-version` option to see the full list of supported versions.
|
||||
You should include all versions that your code supports. If you support Python 3.8
|
||||
through 3.11, you should write:
|
||||
You should include all versions that your code supports. If you support Python 3.11
|
||||
through 3.13, you should write:
|
||||
|
||||
```console
|
||||
$ black -t py38 -t py39 -t py310 -t py311
|
||||
$ black -t py311 -t py312 -t py313
|
||||
```
|
||||
|
||||
In a [configuration file](#configuration-via-a-file), you can write:
|
||||
|
||||
```toml
|
||||
target-version = ["py38", "py39", "py310", "py311"]
|
||||
target-version = ["py311", "py312", "py313"]
|
||||
```
|
||||
|
||||
By default, Black will infer target versions from the project metadata in
|
||||
@ -269,8 +269,8 @@ configuration file for consistent results across environments.
|
||||
|
||||
```console
|
||||
$ black --version
|
||||
black, 24.10.0 (compiled: yes)
|
||||
$ black --required-version 24.10.0 -c "format = 'this'"
|
||||
black, 25.1.0 (compiled: yes)
|
||||
$ black --required-version 25.1.0 -c "format = 'this'"
|
||||
format = "this"
|
||||
$ black --required-version 31.5b2 -c "still = 'beta?!'"
|
||||
Oh no! 💥 💔 💥 The required version does not match the running version!
|
||||
@ -366,7 +366,7 @@ You can check the version of _Black_ you have installed using the `--version` fl
|
||||
|
||||
```console
|
||||
$ black --version
|
||||
black, 24.10.0
|
||||
black, 25.1.0
|
||||
```
|
||||
|
||||
#### `--config`
|
||||
@ -478,9 +478,10 @@ operating system, this configuration file should be stored as:
|
||||
`XDG_CONFIG_HOME` environment variable is not set)
|
||||
|
||||
Note that these are paths to the TOML file itself (meaning that they shouldn't be named
|
||||
as `pyproject.toml`), not directories where you store the configuration. Here, `~`
|
||||
refers to the path to your home directory. On Windows, this will be something like
|
||||
`C:\\Users\UserName`.
|
||||
as `pyproject.toml`), not directories where you store the configuration (i.e.,
|
||||
`black`/`.black` is the file to create and add your configuration options to, in the
|
||||
`~/.config/` directory). Here, `~` refers to the path to your home directory. On
|
||||
Windows, this will be something like `C:\\Users\UserName`.
|
||||
|
||||
You can also explicitly specify the path to a particular file that you want with
|
||||
`--config`. In this situation _Black_ will not look for any other file.
|
||||
|
@ -7,15 +7,16 @@
|
||||
import venv
|
||||
import zipfile
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from collections.abc import Generator
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import lru_cache, partial
|
||||
from pathlib import Path
|
||||
from typing import Generator, List, NamedTuple, Optional, Tuple, Union, cast
|
||||
from typing import NamedTuple, Optional, Union, cast
|
||||
from urllib.request import urlopen, urlretrieve
|
||||
|
||||
PYPI_INSTANCE = "https://pypi.org/pypi"
|
||||
PYPI_TOP_PACKAGES = (
|
||||
"https://hugovk.github.io/top-pypi-packages/top-pypi-packages-30-days.min.json"
|
||||
"https://hugovk.github.io/top-pypi-packages/top-pypi-packages.min.json"
|
||||
)
|
||||
INTERNAL_BLACK_REPO = f"{tempfile.gettempdir()}/__black"
|
||||
|
||||
@ -54,7 +55,7 @@ def get_pypi_download_url(package: str, version: Optional[str]) -> str:
|
||||
return cast(str, source["url"])
|
||||
|
||||
|
||||
def get_top_packages() -> List[str]:
|
||||
def get_top_packages() -> list[str]:
|
||||
with urlopen(PYPI_TOP_PACKAGES) as page:
|
||||
result = json.load(page)
|
||||
|
||||
@ -150,7 +151,7 @@ def git_switch_branch(
|
||||
subprocess.run(args, cwd=repo)
|
||||
|
||||
|
||||
def init_repos(options: Namespace) -> Tuple[Path, ...]:
|
||||
def init_repos(options: Namespace) -> tuple[Path, ...]:
|
||||
options.output.mkdir(exist_ok=True)
|
||||
|
||||
if options.top_packages:
|
||||
@ -206,7 +207,7 @@ def format_repo_with_version(
|
||||
git_switch_branch(black_version.version, repo=black_repo)
|
||||
git_switch_branch(current_branch, repo=repo, new=True, from_branch=from_branch)
|
||||
|
||||
format_cmd: List[Union[Path, str]] = [
|
||||
format_cmd: list[Union[Path, str]] = [
|
||||
black_runner(black_version.version, black_repo),
|
||||
(black_repo / "black.py").resolve(),
|
||||
".",
|
||||
@ -222,7 +223,7 @@ def format_repo_with_version(
|
||||
return current_branch
|
||||
|
||||
|
||||
def format_repos(repos: Tuple[Path, ...], options: Namespace) -> None:
|
||||
def format_repos(repos: tuple[Path, ...], options: Namespace) -> None:
|
||||
black_versions = tuple(
|
||||
BlackVersion(*version.split(":")) for version in options.versions
|
||||
)
|
||||
|
@ -21,7 +21,7 @@ endif
|
||||
|
||||
if v:version < 700 || !has('python3')
|
||||
func! __BLACK_MISSING()
|
||||
echo "The black.vim plugin requires vim7.0+ with Python 3.6 support."
|
||||
echo "The black.vim plugin requires vim7.0+ with Python 3.9 support."
|
||||
endfunc
|
||||
command! Black :call __BLACK_MISSING()
|
||||
command! BlackUpgrade :call __BLACK_MISSING()
|
||||
@ -72,12 +72,11 @@ endif
|
||||
|
||||
function BlackComplete(ArgLead, CmdLine, CursorPos)
|
||||
return [
|
||||
\ 'target_version=py27',
|
||||
\ 'target_version=py36',
|
||||
\ 'target_version=py37',
|
||||
\ 'target_version=py38',
|
||||
\ 'target_version=py39',
|
||||
\ 'target_version=py310',
|
||||
\ 'target_version=py311',
|
||||
\ 'target_version=py312',
|
||||
\ 'target_version=py313',
|
||||
\ ]
|
||||
endfunction
|
||||
|
||||
|
@ -33,7 +33,7 @@ build-backend = "hatchling.build"
|
||||
[project]
|
||||
name = "black"
|
||||
description = "The uncompromising code formatter."
|
||||
license = { text = "MIT" }
|
||||
license = "MIT"
|
||||
requires-python = ">=3.9"
|
||||
authors = [
|
||||
{ name = "Łukasz Langa", email = "lukasz@langa.pl" },
|
||||
@ -69,6 +69,7 @@ dependencies = [
|
||||
"packaging>=22.0",
|
||||
"pathspec>=0.9.0",
|
||||
"platformdirs>=2",
|
||||
"pytokens>=0.1.10",
|
||||
"tomli>=1.1.0; python_version < '3.11'",
|
||||
"typing_extensions>=4.0.1; python_version < '3.11'",
|
||||
]
|
||||
@ -125,7 +126,7 @@ macos-max-compat = true
|
||||
enable-by-default = false
|
||||
dependencies = [
|
||||
"hatch-mypyc>=0.16.0",
|
||||
"mypy @ git+https://github.com/python/mypy@bc8119150e49895f7a496ae7ae7362a2828e7e9e",
|
||||
"mypy>=1.12",
|
||||
"click>=8.1.7",
|
||||
]
|
||||
require-runtime-dependencies = true
|
||||
@ -186,16 +187,6 @@ MYPYC_DEBUG_LEVEL = "0"
|
||||
# Black needs Clang to compile successfully on Linux.
|
||||
CC = "clang"
|
||||
|
||||
[tool.cibuildwheel.macos]
|
||||
build-frontend = { name = "build", args = ["--no-isolation"] }
|
||||
# Unfortunately, hatch doesn't respect MACOSX_DEPLOYMENT_TARGET
|
||||
# Note we don't have a good test for this sed horror, so if you futz with it
|
||||
# make sure to test manually
|
||||
before-build = [
|
||||
"python -m pip install 'hatchling==1.20.0' hatch-vcs hatch-fancy-pypi-readme 'hatch-mypyc>=0.16.0' 'mypy @ git+https://github.com/python/mypy@bc8119150e49895f7a496ae7ae7362a2828e7e9e' 'click>=8.1.7'",
|
||||
"""sed -i '' -e "600,700s/'10_16'/os.environ['MACOSX_DEPLOYMENT_TARGET'].replace('.', '_')/" $(python -c 'import hatchling.builders.wheel as h; print(h.__file__)') """,
|
||||
]
|
||||
|
||||
[tool.isort]
|
||||
atomic = true
|
||||
profile = "black"
|
||||
@ -234,6 +225,8 @@ branch = true
|
||||
python_version = "3.9"
|
||||
mypy_path = "src"
|
||||
strict = true
|
||||
strict_bytes = true
|
||||
local_partial_types = true
|
||||
# Unreachable blocks have been an issue when compiling mypyc, let's try to avoid 'em in the first place.
|
||||
warn_unreachable = true
|
||||
implicit_reexport = true
|
||||
|
@ -5,14 +5,11 @@
|
||||
a coverage-guided fuzzer I'm working on.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import hypothesmith
|
||||
from hypothesis import HealthCheck, given, settings
|
||||
from hypothesis import strategies as st
|
||||
|
||||
import black
|
||||
from blib2to3.pgen2.tokenize import TokenError
|
||||
|
||||
|
||||
# This test uses the Hypothesis and Hypothesmith libraries to generate random
|
||||
@ -45,23 +42,7 @@ def test_idempotent_any_syntatically_valid_python(
|
||||
compile(src_contents, "<string>", "exec") # else the bug is in hypothesmith
|
||||
|
||||
# Then format the code...
|
||||
try:
|
||||
dst_contents = black.format_str(src_contents, mode=mode)
|
||||
except black.InvalidInput:
|
||||
# This is a bug - if it's valid Python code, as above, Black should be
|
||||
# able to cope with it. See issues #970, #1012
|
||||
# TODO: remove this try-except block when issues are resolved.
|
||||
return
|
||||
except TokenError as e:
|
||||
if ( # Special-case logic for backslashes followed by newlines or end-of-input
|
||||
e.args[0] == "EOF in multi-line statement"
|
||||
and re.search(r"\\($|\r?\n)", src_contents) is not None
|
||||
):
|
||||
# This is a bug - if it's valid Python code, as above, Black should be
|
||||
# able to cope with it. See issue #1012.
|
||||
# TODO: remove this block when the issue is resolved.
|
||||
return
|
||||
raise
|
||||
dst_contents = black.format_str(src_contents, mode=mode)
|
||||
|
||||
# And check that we got equivalent and stable output.
|
||||
black.assert_equivalent(src_contents, dst_contents)
|
||||
@ -80,7 +61,7 @@ def test_idempotent_any_syntatically_valid_python(
|
||||
try:
|
||||
import sys
|
||||
|
||||
import atheris # type: ignore[import-not-found]
|
||||
import atheris
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
|
@ -17,13 +17,13 @@
|
||||
"""
|
||||
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from os.path import basename, dirname, join
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
import wcwidth # type: ignore[import-not-found]
|
||||
|
||||
|
||||
def make_width_table() -> Iterable[Tuple[int, int, int]]:
|
||||
def make_width_table() -> Iterable[tuple[int, int, int]]:
|
||||
start_codepoint = -1
|
||||
end_codepoint = -1
|
||||
range_width = -2
|
||||
@ -53,9 +53,9 @@ def main() -> None:
|
||||
f.write(f"""# Generated by {basename(__file__)}
|
||||
# wcwidth {wcwidth.__version__}
|
||||
# Unicode {wcwidth.list_versions()[-1]}
|
||||
from typing import Final, List, Tuple
|
||||
from typing import Final
|
||||
|
||||
WIDTH_TABLE: Final[List[Tuple[int, int, int]]] = [
|
||||
WIDTH_TABLE: Final[list[tuple[int, int, int]]] = [
|
||||
""")
|
||||
for triple in make_width_table():
|
||||
f.write(f" {triple!r},\n")
|
||||
|
@ -77,7 +77,7 @@ def blackify(base_branch: str, black_command: str, logger: logging.Logger) -> in
|
||||
git("commit", "--allow-empty", "-aqC", commit)
|
||||
|
||||
for commit in commits:
|
||||
git("branch", "-qD", "%s-black" % commit)
|
||||
git("branch", "-qD", f"{commit}-black")
|
||||
|
||||
return 0
|
||||
|
||||
|
@ -5,24 +5,22 @@
|
||||
import sys
|
||||
import tokenize
|
||||
import traceback
|
||||
from collections.abc import (
|
||||
Collection,
|
||||
Generator,
|
||||
Iterator,
|
||||
MutableMapping,
|
||||
Sequence,
|
||||
Sized,
|
||||
)
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import replace
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from json.decoder import JSONDecodeError
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Any,
|
||||
Collection,
|
||||
Generator,
|
||||
Iterator,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Pattern,
|
||||
Sequence,
|
||||
Sized,
|
||||
Union,
|
||||
)
|
||||
from re import Pattern
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import click
|
||||
from click.core import ParameterSource
|
||||
@ -751,6 +749,12 @@ def get_sources(
|
||||
for s in src:
|
||||
if s == "-" and stdin_filename:
|
||||
path = Path(stdin_filename)
|
||||
if path_is_excluded(stdin_filename, force_exclude):
|
||||
report.path_ignored(
|
||||
path,
|
||||
"--stdin-filename matches the --force-exclude regular expression",
|
||||
)
|
||||
continue
|
||||
is_stdin = True
|
||||
else:
|
||||
path = Path(s)
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""Builds on top of nodes.py to track brackets."""
|
||||
|
||||
from collections.abc import Iterable, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Final, Iterable, Optional, Sequence, Union
|
||||
from typing import Final, Optional, Union
|
||||
|
||||
from black.nodes import (
|
||||
BRACKET,
|
||||
|
@ -5,9 +5,10 @@
|
||||
import pickle
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Iterable, NamedTuple
|
||||
from typing import NamedTuple
|
||||
|
||||
from platformdirs import user_cache_dir
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import re
|
||||
from collections.abc import Collection, Iterator
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from typing import Collection, Final, Iterator, Optional, Union
|
||||
from typing import Final, Optional, Union
|
||||
|
||||
from black.mode import Mode, Preview
|
||||
from black.nodes import (
|
||||
@ -234,11 +235,7 @@ def convert_one_fmt_off_pair(
|
||||
standalone_comment_prefix += fmt_off_prefix
|
||||
hidden_value = comment.value + "\n" + hidden_value
|
||||
if is_fmt_skip:
|
||||
hidden_value += (
|
||||
comment.leading_whitespace
|
||||
if Preview.no_normalize_fmt_skip_whitespace in mode
|
||||
else " "
|
||||
) + comment.value
|
||||
hidden_value += comment.leading_whitespace + comment.value
|
||||
if hidden_value.endswith("\n"):
|
||||
# That happens when one of the `ignored_nodes` ended with a NEWLINE
|
||||
# leaf (possibly followed by a DEDENT).
|
||||
@ -273,7 +270,7 @@ def generate_ignored_nodes(
|
||||
Stops at the end of the block.
|
||||
"""
|
||||
if _contains_fmt_skip_comment(comment.value, mode):
|
||||
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment)
|
||||
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, mode)
|
||||
return
|
||||
container: Optional[LN] = container_of(leaf)
|
||||
while container is not None and container.type != token.ENDMARKER:
|
||||
@ -312,23 +309,67 @@ def generate_ignored_nodes(
|
||||
|
||||
|
||||
def _generate_ignored_nodes_from_fmt_skip(
|
||||
leaf: Leaf, comment: ProtoComment
|
||||
leaf: Leaf, comment: ProtoComment, mode: Mode
|
||||
) -> Iterator[LN]:
|
||||
"""Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
|
||||
prev_sibling = leaf.prev_sibling
|
||||
parent = leaf.parent
|
||||
ignored_nodes: list[LN] = []
|
||||
# Need to properly format the leaf prefix to compare it to comment.value,
|
||||
# which is also formatted
|
||||
comments = list_comments(leaf.prefix, is_endmarker=False)
|
||||
if not comments or comment.value != comments[0].value:
|
||||
return
|
||||
if prev_sibling is not None:
|
||||
leaf.prefix = ""
|
||||
siblings = [prev_sibling]
|
||||
while "\n" not in prev_sibling.prefix and prev_sibling.prev_sibling is not None:
|
||||
prev_sibling = prev_sibling.prev_sibling
|
||||
siblings.insert(0, prev_sibling)
|
||||
yield from siblings
|
||||
leaf.prefix = leaf.prefix[comment.consumed :]
|
||||
|
||||
if Preview.fix_fmt_skip_in_one_liners not in mode:
|
||||
siblings = [prev_sibling]
|
||||
while (
|
||||
"\n" not in prev_sibling.prefix
|
||||
and prev_sibling.prev_sibling is not None
|
||||
):
|
||||
prev_sibling = prev_sibling.prev_sibling
|
||||
siblings.insert(0, prev_sibling)
|
||||
yield from siblings
|
||||
return
|
||||
|
||||
# Generates the nodes to be ignored by `fmt: skip`.
|
||||
|
||||
# Nodes to ignore are the ones on the same line as the
|
||||
# `# fmt: skip` comment, excluding the `# fmt: skip`
|
||||
# node itself.
|
||||
|
||||
# Traversal process (starting at the `# fmt: skip` node):
|
||||
# 1. Move to the `prev_sibling` of the current node.
|
||||
# 2. If `prev_sibling` has children, go to its rightmost leaf.
|
||||
# 3. If there’s no `prev_sibling`, move up to the parent
|
||||
# node and repeat.
|
||||
# 4. Continue until:
|
||||
# a. You encounter an `INDENT` or `NEWLINE` node (indicates
|
||||
# start of the line).
|
||||
# b. You reach the root node.
|
||||
|
||||
# Include all visited LEAVES in the ignored list, except INDENT
|
||||
# or NEWLINE leaves.
|
||||
|
||||
current_node = prev_sibling
|
||||
ignored_nodes = [current_node]
|
||||
if current_node.prev_sibling is None and current_node.parent is not None:
|
||||
current_node = current_node.parent
|
||||
while "\n" not in current_node.prefix and current_node.prev_sibling is not None:
|
||||
leaf_nodes = list(current_node.prev_sibling.leaves())
|
||||
current_node = leaf_nodes[-1] if leaf_nodes else current_node
|
||||
|
||||
if current_node.type in (token.NEWLINE, token.INDENT):
|
||||
current_node.prefix = ""
|
||||
break
|
||||
|
||||
ignored_nodes.insert(0, current_node)
|
||||
|
||||
if current_node.prev_sibling is None and current_node.parent is not None:
|
||||
current_node = current_node.parent
|
||||
yield from ignored_nodes
|
||||
elif (
|
||||
parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE
|
||||
):
|
||||
@ -336,7 +377,6 @@ def _generate_ignored_nodes_from_fmt_skip(
|
||||
# statements. The ignored nodes should be previous siblings of the
|
||||
# parent suite node.
|
||||
leaf.prefix = ""
|
||||
ignored_nodes: list[LN] = []
|
||||
parent_sibling = parent.prev_sibling
|
||||
while parent_sibling is not None and parent_sibling.type != syms.suite:
|
||||
ignored_nodes.insert(0, parent_sibling)
|
||||
|
@ -10,10 +10,11 @@
|
||||
import signal
|
||||
import sys
|
||||
import traceback
|
||||
from collections.abc import Iterable
|
||||
from concurrent.futures import Executor, ProcessPoolExecutor, ThreadPoolExecutor
|
||||
from multiprocessing import Manager
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from mypy_extensions import mypyc_attr
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Iterator, TypeVar, Union
|
||||
from typing import Any, TypeVar, Union
|
||||
|
||||
from black.nodes import Visitor
|
||||
from black.output import out
|
||||
|
@ -1,18 +1,11 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Iterable, Iterator, Sequence
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Optional,
|
||||
Pattern,
|
||||
Sequence,
|
||||
Union,
|
||||
)
|
||||
from re import Pattern
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
from mypy_extensions import mypyc_attr
|
||||
from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
|
||||
|
@ -43,7 +43,6 @@
|
||||
"time",
|
||||
"timeit",
|
||||
))
|
||||
TOKEN_HEX = secrets.token_hex
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@ -160,7 +159,7 @@ def mask_cell(src: str) -> tuple[str, list[Replacement]]:
|
||||
|
||||
becomes
|
||||
|
||||
"25716f358c32750e"
|
||||
b"25716f358c32750"
|
||||
'foo'
|
||||
|
||||
The replacements are returned, along with the transformed code.
|
||||
@ -178,18 +177,32 @@ def mask_cell(src: str) -> tuple[str, list[Replacement]]:
|
||||
from IPython.core.inputtransformer2 import TransformerManager
|
||||
|
||||
transformer_manager = TransformerManager()
|
||||
# A side effect of the following transformation is that it also removes any
|
||||
# empty lines at the beginning of the cell.
|
||||
transformed = transformer_manager.transform_cell(src)
|
||||
transformed, cell_magic_replacements = replace_cell_magics(transformed)
|
||||
replacements += cell_magic_replacements
|
||||
transformed = transformer_manager.transform_cell(transformed)
|
||||
transformed, magic_replacements = replace_magics(transformed)
|
||||
if len(transformed.splitlines()) != len(src.splitlines()):
|
||||
if len(transformed.strip().splitlines()) != len(src.strip().splitlines()):
|
||||
# Multi-line magic, not supported.
|
||||
raise NothingChanged
|
||||
replacements += magic_replacements
|
||||
return transformed, replacements
|
||||
|
||||
|
||||
def create_token(n_chars: int) -> str:
|
||||
"""Create a randomly generated token that is n_chars characters long."""
|
||||
assert n_chars > 0
|
||||
n_bytes = max(n_chars // 2 - 1, 1)
|
||||
token = secrets.token_hex(n_bytes)
|
||||
if len(token) + 3 > n_chars:
|
||||
token = token[:-1]
|
||||
# We use a bytestring so that the string does not get interpreted
|
||||
# as a docstring.
|
||||
return f'b"{token}"'
|
||||
|
||||
|
||||
def get_token(src: str, magic: str) -> str:
|
||||
"""Return randomly generated token to mask IPython magic with.
|
||||
|
||||
@ -199,11 +212,11 @@ def get_token(src: str, magic: str) -> str:
|
||||
not already present anywhere else in the cell.
|
||||
"""
|
||||
assert magic
|
||||
nbytes = max(len(magic) // 2 - 1, 1)
|
||||
token = TOKEN_HEX(nbytes)
|
||||
n_chars = len(magic)
|
||||
token = create_token(n_chars)
|
||||
counter = 0
|
||||
while token in src:
|
||||
token = TOKEN_HEX(nbytes)
|
||||
token = create_token(n_chars)
|
||||
counter += 1
|
||||
if counter > 100:
|
||||
raise AssertionError(
|
||||
@ -211,9 +224,7 @@ def get_token(src: str, magic: str) -> str:
|
||||
"Please report a bug on https://github.com/psf/black/issues. "
|
||||
f"The magic might be helpful: {magic}"
|
||||
) from None
|
||||
if len(token) + 2 < len(magic):
|
||||
token = f"{token}."
|
||||
return f'"{token}"'
|
||||
return token
|
||||
|
||||
|
||||
def replace_cell_magics(src: str) -> tuple[str, list[Replacement]]:
|
||||
@ -269,7 +280,7 @@ def replace_magics(src: str) -> tuple[str, list[Replacement]]:
|
||||
magic_finder = MagicFinder()
|
||||
magic_finder.visit(ast.parse(src))
|
||||
new_srcs = []
|
||||
for i, line in enumerate(src.splitlines(), start=1):
|
||||
for i, line in enumerate(src.split("\n"), start=1):
|
||||
if i in magic_finder.magics:
|
||||
offsets_and_magics = magic_finder.magics[i]
|
||||
if len(offsets_and_magics) != 1: # pragma: nocover
|
||||
|
@ -4,10 +4,11 @@
|
||||
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Collection, Iterator
|
||||
from dataclasses import replace
|
||||
from enum import Enum, auto
|
||||
from functools import partial, wraps
|
||||
from typing import Collection, Iterator, Optional, Union, cast
|
||||
from typing import Optional, Union, cast
|
||||
|
||||
from black.brackets import (
|
||||
COMMA_PRIORITY,
|
||||
@ -39,11 +40,13 @@
|
||||
ensure_visible,
|
||||
fstring_to_string,
|
||||
get_annotation_type,
|
||||
has_sibling_with_type,
|
||||
is_arith_like,
|
||||
is_async_stmt_or_funcdef,
|
||||
is_atom_with_invisible_parens,
|
||||
is_docstring,
|
||||
is_empty_tuple,
|
||||
is_generator,
|
||||
is_lpar_token,
|
||||
is_multiline_string,
|
||||
is_name_token,
|
||||
@ -54,6 +57,8 @@
|
||||
is_rpar_token,
|
||||
is_stub_body,
|
||||
is_stub_suite,
|
||||
is_tuple,
|
||||
is_tuple_containing_star,
|
||||
is_tuple_containing_walrus,
|
||||
is_type_ignore_comment_string,
|
||||
is_vararg,
|
||||
@ -64,7 +69,7 @@
|
||||
)
|
||||
from black.numerics import normalize_numeric_literal
|
||||
from black.strings import (
|
||||
fix_docstring,
|
||||
fix_multiline_docstring,
|
||||
get_string_prefix,
|
||||
normalize_string_prefix,
|
||||
normalize_string_quotes,
|
||||
@ -411,10 +416,9 @@ def foo(a: (int), b: (float) = 7): ...
|
||||
yield from self.visit_default(node)
|
||||
|
||||
def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
|
||||
if Preview.hex_codes_in_unicode_sequences in self.mode:
|
||||
normalize_unicode_escape_sequences(leaf)
|
||||
normalize_unicode_escape_sequences(leaf)
|
||||
|
||||
if is_docstring(leaf, self.mode) and not re.search(r"\\\s*\n", leaf.value):
|
||||
if is_docstring(leaf) and not re.search(r"\\\s*\n", leaf.value):
|
||||
# We're ignoring docstrings with backslash newline escapes because changing
|
||||
# indentation of those changes the AST representation of the code.
|
||||
if self.mode.string_normalization:
|
||||
@ -441,7 +445,7 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
|
||||
indent = " " * 4 * self.current_line.depth
|
||||
|
||||
if is_multiline_string(leaf):
|
||||
docstring = fix_docstring(docstring, indent)
|
||||
docstring = fix_multiline_docstring(docstring, indent)
|
||||
else:
|
||||
docstring = docstring.strip()
|
||||
|
||||
@ -485,10 +489,7 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
|
||||
and len(indent) + quote_len <= self.mode.line_length
|
||||
and not has_trailing_backslash
|
||||
):
|
||||
if (
|
||||
Preview.docstring_check_for_newline in self.mode
|
||||
and leaf.value[-1 - quote_len] == "\n"
|
||||
):
|
||||
if leaf.value[-1 - quote_len] == "\n":
|
||||
leaf.value = prefix + quote + docstring + quote
|
||||
else:
|
||||
leaf.value = prefix + quote + docstring + "\n" + indent + quote
|
||||
@ -506,6 +507,19 @@ def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]:
|
||||
normalize_numeric_literal(leaf)
|
||||
yield from self.visit_default(leaf)
|
||||
|
||||
def visit_atom(self, node: Node) -> Iterator[Line]:
|
||||
"""Visit any atom"""
|
||||
if len(node.children) == 3:
|
||||
first = node.children[0]
|
||||
last = node.children[-1]
|
||||
if (first.type == token.LSQB and last.type == token.RSQB) or (
|
||||
first.type == token.LBRACE and last.type == token.RBRACE
|
||||
):
|
||||
# Lists or sets of one item
|
||||
maybe_make_parens_invisible_in_atom(node.children[1], parent=node)
|
||||
|
||||
yield from self.visit_default(node)
|
||||
|
||||
def visit_fstring(self, node: Node) -> Iterator[Line]:
|
||||
# currently we don't want to format and split f-strings at all.
|
||||
string_leaf = fstring_to_string(node)
|
||||
@ -583,8 +597,7 @@ def __post_init__(self) -> None:
|
||||
# PEP 634
|
||||
self.visit_match_stmt = self.visit_match_case
|
||||
self.visit_case_block = self.visit_match_case
|
||||
if Preview.remove_redundant_guard_parens in self.mode:
|
||||
self.visit_guard = partial(v, keywords=Ø, parens={"if"})
|
||||
self.visit_guard = partial(v, keywords=Ø, parens={"if"})
|
||||
|
||||
|
||||
def _hugging_power_ops_line_to_string(
|
||||
@ -768,26 +781,29 @@ def left_hand_split(
|
||||
Prefer RHS otherwise. This is why this function is not symmetrical with
|
||||
:func:`right_hand_split` which also handles optional parentheses.
|
||||
"""
|
||||
tail_leaves: list[Leaf] = []
|
||||
body_leaves: list[Leaf] = []
|
||||
head_leaves: list[Leaf] = []
|
||||
current_leaves = head_leaves
|
||||
matching_bracket: Optional[Leaf] = None
|
||||
for leaf in line.leaves:
|
||||
if (
|
||||
current_leaves is body_leaves
|
||||
and leaf.type in CLOSING_BRACKETS
|
||||
and leaf.opening_bracket is matching_bracket
|
||||
and isinstance(matching_bracket, Leaf)
|
||||
):
|
||||
ensure_visible(leaf)
|
||||
ensure_visible(matching_bracket)
|
||||
current_leaves = tail_leaves if body_leaves else head_leaves
|
||||
current_leaves.append(leaf)
|
||||
if current_leaves is head_leaves:
|
||||
if leaf.type in OPENING_BRACKETS:
|
||||
matching_bracket = leaf
|
||||
current_leaves = body_leaves
|
||||
for leaf_type in [token.LPAR, token.LSQB]:
|
||||
tail_leaves: list[Leaf] = []
|
||||
body_leaves: list[Leaf] = []
|
||||
head_leaves: list[Leaf] = []
|
||||
current_leaves = head_leaves
|
||||
matching_bracket: Optional[Leaf] = None
|
||||
for leaf in line.leaves:
|
||||
if (
|
||||
current_leaves is body_leaves
|
||||
and leaf.type in CLOSING_BRACKETS
|
||||
and leaf.opening_bracket is matching_bracket
|
||||
and isinstance(matching_bracket, Leaf)
|
||||
):
|
||||
ensure_visible(leaf)
|
||||
ensure_visible(matching_bracket)
|
||||
current_leaves = tail_leaves if body_leaves else head_leaves
|
||||
current_leaves.append(leaf)
|
||||
if current_leaves is head_leaves:
|
||||
if leaf.type == leaf_type:
|
||||
matching_bracket = leaf
|
||||
current_leaves = body_leaves
|
||||
if matching_bracket and tail_leaves:
|
||||
break
|
||||
if not matching_bracket or not tail_leaves:
|
||||
raise CannotSplit("No brackets found")
|
||||
|
||||
@ -954,29 +970,7 @@ def _maybe_split_omitting_optional_parens(
|
||||
try:
|
||||
# The RHSResult Omitting Optional Parens.
|
||||
rhs_oop = _first_right_hand_split(line, omit=omit)
|
||||
is_split_right_after_equal = (
|
||||
len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL
|
||||
)
|
||||
rhs_head_contains_brackets = any(
|
||||
leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]
|
||||
)
|
||||
# the -1 is for the ending optional paren
|
||||
rhs_head_short_enough = is_line_short_enough(
|
||||
rhs.head, mode=replace(mode, line_length=mode.line_length - 1)
|
||||
)
|
||||
rhs_head_explode_blocked_by_magic_trailing_comma = (
|
||||
rhs.head.magic_trailing_comma is None
|
||||
)
|
||||
if (
|
||||
not (
|
||||
is_split_right_after_equal
|
||||
and rhs_head_contains_brackets
|
||||
and rhs_head_short_enough
|
||||
and rhs_head_explode_blocked_by_magic_trailing_comma
|
||||
)
|
||||
# the omit optional parens split is preferred by some other reason
|
||||
or _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode)
|
||||
):
|
||||
if _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode):
|
||||
yield from _maybe_split_omitting_optional_parens(
|
||||
rhs_oop, line, mode, features=features, omit=omit
|
||||
)
|
||||
@ -987,8 +981,15 @@ def _maybe_split_omitting_optional_parens(
|
||||
if line.is_chained_assignment:
|
||||
pass
|
||||
|
||||
elif not can_be_split(rhs.body) and not is_line_short_enough(
|
||||
rhs.body, mode=mode
|
||||
elif (
|
||||
not can_be_split(rhs.body)
|
||||
and not is_line_short_enough(rhs.body, mode=mode)
|
||||
and not (
|
||||
Preview.wrap_long_dict_values_in_parens
|
||||
and rhs.opening_bracket.parent
|
||||
and rhs.opening_bracket.parent.parent
|
||||
and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker
|
||||
)
|
||||
):
|
||||
raise CannotSplit(
|
||||
"Splitting failed, body is still too long and can't be split."
|
||||
@ -1019,6 +1020,44 @@ def _prefer_split_rhs_oop_over_rhs(
|
||||
Returns whether we should prefer the result from a split omitting optional parens
|
||||
(rhs_oop) over the original (rhs).
|
||||
"""
|
||||
# contains unsplittable type ignore
|
||||
if (
|
||||
rhs_oop.head.contains_unsplittable_type_ignore()
|
||||
or rhs_oop.body.contains_unsplittable_type_ignore()
|
||||
or rhs_oop.tail.contains_unsplittable_type_ignore()
|
||||
):
|
||||
return True
|
||||
|
||||
# Retain optional parens around dictionary values
|
||||
if (
|
||||
Preview.wrap_long_dict_values_in_parens
|
||||
and rhs.opening_bracket.parent
|
||||
and rhs.opening_bracket.parent.parent
|
||||
and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker
|
||||
and rhs.body.bracket_tracker.delimiters
|
||||
):
|
||||
# Unless the split is inside the key
|
||||
return any(leaf.type == token.COLON for leaf in rhs_oop.tail.leaves)
|
||||
|
||||
# the split is right after `=`
|
||||
if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL):
|
||||
return True
|
||||
|
||||
# the left side of assignment contains brackets
|
||||
if not any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]):
|
||||
return True
|
||||
|
||||
# the left side of assignment is short enough (the -1 is for the ending optional
|
||||
# paren)
|
||||
if not is_line_short_enough(
|
||||
rhs.head, mode=replace(mode, line_length=mode.line_length - 1)
|
||||
):
|
||||
return True
|
||||
|
||||
# the left side of assignment won't explode further because of magic trailing comma
|
||||
if rhs.head.magic_trailing_comma is not None:
|
||||
return True
|
||||
|
||||
# If we have multiple targets, we prefer more `=`s on the head vs pushing them to
|
||||
# the body
|
||||
rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL)
|
||||
@ -1046,10 +1085,6 @@ def _prefer_split_rhs_oop_over_rhs(
|
||||
# the first line is short enough
|
||||
and is_line_short_enough(rhs_oop.head, mode=mode)
|
||||
)
|
||||
# contains unsplittable type ignore
|
||||
or rhs_oop.head.contains_unsplittable_type_ignore()
|
||||
or rhs_oop.body.contains_unsplittable_type_ignore()
|
||||
or rhs_oop.tail.contains_unsplittable_type_ignore()
|
||||
)
|
||||
|
||||
|
||||
@ -1094,12 +1129,7 @@ def _ensure_trailing_comma(
|
||||
return False
|
||||
# Don't add commas if we already have any commas
|
||||
if any(
|
||||
leaf.type == token.COMMA
|
||||
and (
|
||||
Preview.typed_params_trailing_comma not in original.mode
|
||||
or not is_part_of_annotation(leaf)
|
||||
)
|
||||
for leaf in leaves
|
||||
leaf.type == token.COMMA and not is_part_of_annotation(leaf) for leaf in leaves
|
||||
):
|
||||
return False
|
||||
|
||||
@ -1380,11 +1410,7 @@ def normalize_invisible_parens( # noqa: C901
|
||||
)
|
||||
|
||||
# Add parentheses around if guards in case blocks
|
||||
if (
|
||||
isinstance(child, Node)
|
||||
and child.type == syms.guard
|
||||
and Preview.parens_for_long_if_clauses_in_case_block in mode
|
||||
):
|
||||
if isinstance(child, Node) and child.type == syms.guard:
|
||||
normalize_invisible_parens(
|
||||
child, parens_after={"if"}, mode=mode, features=features
|
||||
)
|
||||
@ -1602,6 +1628,12 @@ def maybe_make_parens_invisible_in_atom(
|
||||
node.type not in (syms.atom, syms.expr)
|
||||
or is_empty_tuple(node)
|
||||
or is_one_tuple(node)
|
||||
or (is_tuple(node) and parent.type == syms.asexpr_test)
|
||||
or (
|
||||
is_tuple(node)
|
||||
and parent.type == syms.with_stmt
|
||||
and has_sibling_with_type(node, token.COMMA)
|
||||
)
|
||||
or (is_yield(node) and parent.type != syms.expr_stmt)
|
||||
or (
|
||||
# This condition tries to prevent removing non-optional brackets
|
||||
@ -1611,6 +1643,8 @@ def maybe_make_parens_invisible_in_atom(
|
||||
and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY
|
||||
)
|
||||
or is_tuple_containing_walrus(node)
|
||||
or is_tuple_containing_star(node)
|
||||
or is_generator(node)
|
||||
):
|
||||
return False
|
||||
|
||||
@ -1623,6 +1657,7 @@ def maybe_make_parens_invisible_in_atom(
|
||||
syms.except_clause,
|
||||
syms.funcdef,
|
||||
syms.with_stmt,
|
||||
syms.testlist_gexp,
|
||||
syms.tname,
|
||||
# these ones aren't useful to end users, but they do please fuzzers
|
||||
syms.for_stmt,
|
||||
@ -1642,9 +1677,6 @@ def maybe_make_parens_invisible_in_atom(
|
||||
not is_type_ignore_comment_string(middle.prefix.strip())
|
||||
):
|
||||
first.value = ""
|
||||
if first.prefix.strip():
|
||||
# Preserve comments before first paren
|
||||
middle.prefix = first.prefix + middle.prefix
|
||||
last.value = ""
|
||||
maybe_make_parens_invisible_in_atom(
|
||||
middle,
|
||||
@ -1656,6 +1688,13 @@ def maybe_make_parens_invisible_in_atom(
|
||||
# Strip the invisible parens from `middle` by replacing
|
||||
# it with the child in-between the invisible parens
|
||||
middle.replace(middle.children[1])
|
||||
|
||||
if middle.children[0].prefix.strip():
|
||||
# Preserve comments before first paren
|
||||
middle.children[1].prefix = (
|
||||
middle.children[0].prefix + middle.children[1].prefix
|
||||
)
|
||||
|
||||
if middle.children[-1].prefix.strip():
|
||||
# Preserve comments before last paren
|
||||
last.prefix = middle.children[-1].prefix + last.prefix
|
||||
|
@ -1,7 +1,8 @@
|
||||
import itertools
|
||||
import math
|
||||
from collections.abc import Callable, Iterator, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Callable, Iterator, Optional, Sequence, TypeVar, Union, cast
|
||||
from typing import Optional, TypeVar, Union, cast
|
||||
|
||||
from black.brackets import COMMA_PRIORITY, DOT_PRIORITY, BracketTracker
|
||||
from black.mode import Mode, Preview
|
||||
@ -203,9 +204,7 @@ def _is_triple_quoted_string(self) -> bool:
|
||||
@property
|
||||
def is_docstring(self) -> bool:
|
||||
"""Is the line a docstring?"""
|
||||
if Preview.unify_docstring_detection not in self.mode:
|
||||
return self._is_triple_quoted_string
|
||||
return bool(self) and is_docstring(self.leaves[0], self.mode)
|
||||
return bool(self) and is_docstring(self.leaves[0])
|
||||
|
||||
@property
|
||||
def is_chained_assignment(self) -> bool:
|
||||
@ -670,6 +669,15 @@ def _maybe_empty_lines(self, current_line: Line) -> tuple[int, int]: # noqa: C9
|
||||
current_line, before, user_had_newline
|
||||
)
|
||||
|
||||
if (
|
||||
self.previous_line.is_import
|
||||
and self.previous_line.depth == 0
|
||||
and current_line.depth == 0
|
||||
and not current_line.is_import
|
||||
and Preview.always_one_newline_after_import in self.mode
|
||||
):
|
||||
return 1, 0
|
||||
|
||||
if (
|
||||
self.previous_line.is_import
|
||||
and not current_line.is_import
|
||||
|
@ -196,28 +196,19 @@ def supports_feature(target_versions: set[TargetVersion], feature: Feature) -> b
|
||||
class Preview(Enum):
|
||||
"""Individual preview style features."""
|
||||
|
||||
hex_codes_in_unicode_sequences = auto()
|
||||
# NOTE: string_processing requires wrap_long_dict_values_in_parens
|
||||
# for https://github.com/psf/black/issues/3117 to be fixed.
|
||||
string_processing = auto()
|
||||
hug_parens_with_braces_and_square_brackets = auto()
|
||||
unify_docstring_detection = auto()
|
||||
no_normalize_fmt_skip_whitespace = auto()
|
||||
wrap_long_dict_values_in_parens = auto()
|
||||
multiline_string_handling = auto()
|
||||
typed_params_trailing_comma = auto()
|
||||
is_simple_lookup_for_doublestar_expression = auto()
|
||||
docstring_check_for_newline = auto()
|
||||
remove_redundant_guard_parens = auto()
|
||||
parens_for_long_if_clauses_in_case_block = auto()
|
||||
pep646_typed_star_arg_type_var_tuple = auto()
|
||||
always_one_newline_after_import = auto()
|
||||
fix_fmt_skip_in_one_liners = auto()
|
||||
|
||||
|
||||
UNSTABLE_FEATURES: set[Preview] = {
|
||||
# Many issues, see summary in https://github.com/psf/black/issues/4042
|
||||
Preview.string_processing,
|
||||
# See issues #3452 and #4158
|
||||
Preview.wrap_long_dict_values_in_parens,
|
||||
# See issue #4159
|
||||
Preview.multiline_string_handling,
|
||||
# See issue #4036 (crash), #4098, #4099 (proposed tweaks)
|
||||
|
@ -3,7 +3,8 @@
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Final, Generic, Iterator, Literal, Optional, TypeVar, Union
|
||||
from collections.abc import Iterator
|
||||
from typing import Final, Generic, Literal, Optional, TypeVar, Union
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeGuard
|
||||
@ -13,7 +14,7 @@
|
||||
from mypy_extensions import mypyc_attr
|
||||
|
||||
from black.cache import CACHE_DIR
|
||||
from black.mode import Mode, Preview
|
||||
from black.mode import Mode
|
||||
from black.strings import get_string_prefix, has_triple_quotes
|
||||
from blib2to3 import pygram
|
||||
from blib2to3.pgen2 import token
|
||||
@ -243,13 +244,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool, mode: Mode) -> str: # no
|
||||
elif (
|
||||
prevp.type == token.STAR
|
||||
and parent_type(prevp) == syms.star_expr
|
||||
and (
|
||||
parent_type(prevp.parent) == syms.subscriptlist
|
||||
or (
|
||||
Preview.pep646_typed_star_arg_type_var_tuple in mode
|
||||
and parent_type(prevp.parent) == syms.tname_star
|
||||
)
|
||||
)
|
||||
and parent_type(prevp.parent) in (syms.subscriptlist, syms.tname_star)
|
||||
):
|
||||
# No space between typevar tuples or unpacking them.
|
||||
return NO
|
||||
@ -550,7 +545,7 @@ def is_arith_like(node: LN) -> bool:
|
||||
}
|
||||
|
||||
|
||||
def is_docstring(node: NL, mode: Mode) -> bool:
|
||||
def is_docstring(node: NL) -> bool:
|
||||
if isinstance(node, Leaf):
|
||||
if node.type != token.STRING:
|
||||
return False
|
||||
@ -560,8 +555,7 @@ def is_docstring(node: NL, mode: Mode) -> bool:
|
||||
return False
|
||||
|
||||
if (
|
||||
Preview.unify_docstring_detection in mode
|
||||
and node.parent
|
||||
node.parent
|
||||
and node.parent.type == syms.simple_stmt
|
||||
and not node.parent.prev_sibling
|
||||
and node.parent.parent
|
||||
@ -609,6 +603,17 @@ def is_one_tuple(node: LN) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def is_tuple(node: LN) -> bool:
|
||||
"""Return True if `node` holds a tuple."""
|
||||
if node.type != syms.atom:
|
||||
return False
|
||||
gexp = unwrap_singleton_parenthesis(node)
|
||||
if gexp is None or gexp.type != syms.testlist_gexp:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_tuple_containing_walrus(node: LN) -> bool:
|
||||
"""Return True if `node` holds a tuple that contains a walrus operator."""
|
||||
if node.type != syms.atom:
|
||||
@ -620,6 +625,28 @@ def is_tuple_containing_walrus(node: LN) -> bool:
|
||||
return any(child.type == syms.namedexpr_test for child in gexp.children)
|
||||
|
||||
|
||||
def is_tuple_containing_star(node: LN) -> bool:
|
||||
"""Return True if `node` holds a tuple that contains a star operator."""
|
||||
if node.type != syms.atom:
|
||||
return False
|
||||
gexp = unwrap_singleton_parenthesis(node)
|
||||
if gexp is None or gexp.type != syms.testlist_gexp:
|
||||
return False
|
||||
|
||||
return any(child.type == syms.star_expr for child in gexp.children)
|
||||
|
||||
|
||||
def is_generator(node: LN) -> bool:
|
||||
"""Return True if `node` holds a generator."""
|
||||
if node.type != syms.atom:
|
||||
return False
|
||||
gexp = unwrap_singleton_parenthesis(node)
|
||||
if gexp is None or gexp.type != syms.testlist_gexp:
|
||||
return False
|
||||
|
||||
return any(child.type == syms.old_comp_for for child in gexp.children)
|
||||
|
||||
|
||||
def is_one_sequence_between(
|
||||
opening: Leaf,
|
||||
closing: Leaf,
|
||||
@ -1031,3 +1058,21 @@ def furthest_ancestor_with_last_leaf(leaf: Leaf) -> LN:
|
||||
while node.parent and node.parent.children and node is node.parent.children[-1]:
|
||||
node = node.parent
|
||||
return node
|
||||
|
||||
|
||||
def has_sibling_with_type(node: LN, type: int) -> bool:
|
||||
# Check previous siblings
|
||||
sibling = node.prev_sibling
|
||||
while sibling is not None:
|
||||
if sibling.type == type:
|
||||
return True
|
||||
sibling = sibling.prev_sibling
|
||||
|
||||
# Check next siblings
|
||||
sibling = node.next_sibling
|
||||
while sibling is not None:
|
||||
if sibling.type == type:
|
||||
return True
|
||||
sibling = sibling.next_sibling
|
||||
|
||||
return False
|
||||
|
@ -5,7 +5,7 @@
|
||||
import ast
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Collection, Iterator
|
||||
from collections.abc import Collection, Iterator
|
||||
|
||||
from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature
|
||||
from black.nodes import syms
|
||||
@ -213,7 +213,7 @@ def _stringify_ast(node: ast.AST, parent_stack: list[ast.AST]) -> Iterator[str]:
|
||||
and isinstance(node, ast.Delete)
|
||||
and isinstance(item, ast.Tuple)
|
||||
):
|
||||
for elt in item.elts:
|
||||
for elt in _unwrap_tuples(item):
|
||||
yield from _stringify_ast_with_new_parent(
|
||||
elt, parent_stack, node
|
||||
)
|
||||
@ -250,3 +250,11 @@ def _stringify_ast(node: ast.AST, parent_stack: list[ast.AST]) -> Iterator[str]:
|
||||
)
|
||||
|
||||
yield f"{' ' * len(parent_stack)}) # /{node.__class__.__name__}"
|
||||
|
||||
|
||||
def _unwrap_tuples(node: ast.Tuple) -> Iterator[ast.AST]:
|
||||
for elt in node.elts:
|
||||
if isinstance(elt, ast.Tuple):
|
||||
yield from _unwrap_tuples(elt)
|
||||
else:
|
||||
yield elt
|
||||
|
@ -1,8 +1,9 @@
|
||||
"""Functions related to Black's formatting by line ranges feature."""
|
||||
|
||||
import difflib
|
||||
from collections.abc import Collection, Iterator, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Collection, Iterator, Sequence, Union
|
||||
from typing import Union
|
||||
|
||||
from black.nodes import (
|
||||
LN,
|
||||
|
@ -79,19 +79,12 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"enum": [
|
||||
"hex_codes_in_unicode_sequences",
|
||||
"string_processing",
|
||||
"hug_parens_with_braces_and_square_brackets",
|
||||
"unify_docstring_detection",
|
||||
"no_normalize_fmt_skip_whitespace",
|
||||
"wrap_long_dict_values_in_parens",
|
||||
"multiline_string_handling",
|
||||
"typed_params_trailing_comma",
|
||||
"is_simple_lookup_for_doublestar_expression",
|
||||
"docstring_check_for_newline",
|
||||
"remove_redundant_guard_parens",
|
||||
"parens_for_long_if_clauses_in_case_block",
|
||||
"pep646_typed_star_arg_type_var_tuple"
|
||||
"always_one_newline_after_import",
|
||||
"fix_fmt_skip_in_one_liners"
|
||||
]
|
||||
},
|
||||
"description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features."
|
||||
|
@ -5,7 +5,8 @@
|
||||
import re
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import Final, Match, Pattern
|
||||
from re import Match, Pattern
|
||||
from typing import Final
|
||||
|
||||
from black._width_table import WIDTH_TABLE
|
||||
from blib2to3.pytree import Leaf
|
||||
@ -62,10 +63,9 @@ def lines_with_leading_tabs_expanded(s: str) -> list[str]:
|
||||
return lines
|
||||
|
||||
|
||||
def fix_docstring(docstring: str, prefix: str) -> str:
|
||||
def fix_multiline_docstring(docstring: str, prefix: str) -> str:
|
||||
# https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation
|
||||
if not docstring:
|
||||
return ""
|
||||
assert docstring, "INTERNAL ERROR: Multiline docstrings cannot be empty"
|
||||
lines = lines_with_leading_tabs_expanded(docstring)
|
||||
# Determine minimum indentation (first line doesn't count):
|
||||
indent = sys.maxsize
|
||||
@ -185,8 +185,7 @@ def normalize_string_quotes(s: str) -> str:
|
||||
orig_quote = "'"
|
||||
new_quote = '"'
|
||||
first_quote_pos = s.find(orig_quote)
|
||||
if first_quote_pos == -1:
|
||||
return s # There's an internal error
|
||||
assert first_quote_pos != -1, f"INTERNAL ERROR: Malformed string {s!r}"
|
||||
|
||||
prefix = s[:first_quote_pos]
|
||||
unescaped_new_quote = _cached_compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
|
||||
|
@ -5,27 +5,15 @@
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Collection, Iterable, Iterator, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Collection,
|
||||
Final,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Literal,
|
||||
Optional,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import Any, ClassVar, Final, Literal, Optional, TypeVar, Union
|
||||
|
||||
from mypy_extensions import trait
|
||||
|
||||
from black.comments import contains_pragma_comment
|
||||
from black.lines import Line, append_leaves
|
||||
from black.mode import Feature, Mode, Preview
|
||||
from black.mode import Feature, Mode
|
||||
from black.nodes import (
|
||||
CLOSING_BRACKETS,
|
||||
OPENING_BRACKETS,
|
||||
@ -94,18 +82,12 @@ def is_simple_lookup(index: int, kind: Literal[1, -1]) -> bool:
|
||||
# Brackets and parentheses indicate calls, subscripts, etc. ...
|
||||
# basically stuff that doesn't count as "simple". Only a NAME lookup
|
||||
# or dotted lookup (eg. NAME.NAME) is OK.
|
||||
if Preview.is_simple_lookup_for_doublestar_expression not in mode:
|
||||
return original_is_simple_lookup_func(line, index, kind)
|
||||
|
||||
if kind == -1:
|
||||
return handle_is_simple_look_up_prev(line, index, {token.RPAR, token.RSQB})
|
||||
else:
|
||||
if kind == -1:
|
||||
return handle_is_simple_look_up_prev(
|
||||
line, index, {token.RPAR, token.RSQB}
|
||||
)
|
||||
else:
|
||||
return handle_is_simple_lookup_forward(
|
||||
line, index, {token.LPAR, token.LSQB}
|
||||
)
|
||||
return handle_is_simple_lookup_forward(
|
||||
line, index, {token.LPAR, token.LSQB}
|
||||
)
|
||||
|
||||
def is_simple_operand(index: int, kind: Literal[1, -1]) -> bool:
|
||||
# An operand is considered "simple" if's a NAME, a numeric CONSTANT, a simple
|
||||
@ -151,30 +133,6 @@ def is_simple_operand(index: int, kind: Literal[1, -1]) -> bool:
|
||||
yield new_line
|
||||
|
||||
|
||||
def original_is_simple_lookup_func(
|
||||
line: Line, index: int, step: Literal[1, -1]
|
||||
) -> bool:
|
||||
if step == -1:
|
||||
disallowed = {token.RPAR, token.RSQB}
|
||||
else:
|
||||
disallowed = {token.LPAR, token.LSQB}
|
||||
|
||||
while 0 <= index < len(line.leaves):
|
||||
current = line.leaves[index]
|
||||
if current.type in disallowed:
|
||||
return False
|
||||
if current.type not in {token.NAME, token.DOT} or current.value == "for":
|
||||
# If the current token isn't disallowed, we'll assume this is
|
||||
# simple as only the disallowed tokens are semantically
|
||||
# attached to this lookup expression we're checking. Also,
|
||||
# stop early if we hit the 'for' bit of a comprehension.
|
||||
return True
|
||||
|
||||
index += step
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def handle_is_simple_look_up_prev(line: Line, index: int, disallowed: set[int]) -> bool:
|
||||
"""
|
||||
Handling the determination of is_simple_lookup for the lines prior to the doublestar
|
||||
@ -672,10 +630,10 @@ def make_naked(string: str, string_prefix: str) -> str:
|
||||
"""
|
||||
assert_is_leaf_string(string)
|
||||
if "f" in string_prefix:
|
||||
f_expressions = (
|
||||
f_expressions = [
|
||||
string[span[0] + 1 : span[1] - 1] # +-1 to get rid of curly braces
|
||||
for span in iter_fexpr_spans(string)
|
||||
)
|
||||
]
|
||||
debug_expressions_contain_visible_quotes = any(
|
||||
re.search(r".*[\'\"].*(?<![!:=])={1}(?!=)(?![^\s:])", expression)
|
||||
for expression in f_expressions
|
||||
@ -806,6 +764,8 @@ def _validate_msg(line: Line, string_idx: int) -> TResult[None]:
|
||||
- The set of all string prefixes in the string group is of
|
||||
length greater than one and is not equal to {"", "f"}.
|
||||
- The string group consists of raw strings.
|
||||
- The string group would merge f-strings with different quote types
|
||||
and internal quotes.
|
||||
- The string group is stringified type annotations. We don't want to
|
||||
process stringified type annotations since pyright doesn't support
|
||||
them spanning multiple string values. (NOTE: mypy, pytype, pyre do
|
||||
@ -832,6 +792,8 @@ def _validate_msg(line: Line, string_idx: int) -> TResult[None]:
|
||||
|
||||
i += inc
|
||||
|
||||
QUOTE = line.leaves[string_idx].value[-1]
|
||||
|
||||
num_of_inline_string_comments = 0
|
||||
set_of_prefixes = set()
|
||||
num_of_strings = 0
|
||||
@ -854,6 +816,19 @@ def _validate_msg(line: Line, string_idx: int) -> TResult[None]:
|
||||
|
||||
set_of_prefixes.add(prefix)
|
||||
|
||||
if (
|
||||
"f" in prefix
|
||||
and leaf.value[-1] != QUOTE
|
||||
and (
|
||||
"'" in leaf.value[len(prefix) + 1 : -1]
|
||||
or '"' in leaf.value[len(prefix) + 1 : -1]
|
||||
)
|
||||
):
|
||||
return TErr(
|
||||
"StringMerger does NOT merge f-strings with different quote types"
|
||||
" and internal quotes."
|
||||
)
|
||||
|
||||
if id(leaf) in line.comments:
|
||||
num_of_inline_string_comments += 1
|
||||
if contains_pragma_comment(line.comments[id(leaf)]):
|
||||
@ -882,6 +857,7 @@ class StringParenStripper(StringTransformer):
|
||||
The line contains a string which is surrounded by parentheses and:
|
||||
- The target string is NOT the only argument to a function call.
|
||||
- The target string is NOT a "pointless" string.
|
||||
- The target string is NOT a dictionary value.
|
||||
- If the target string contains a PERCENT, the brackets are not
|
||||
preceded or followed by an operator with higher precedence than
|
||||
PERCENT.
|
||||
@ -929,11 +905,14 @@ def do_match(self, line: Line) -> TMatchResult:
|
||||
):
|
||||
continue
|
||||
|
||||
# That LPAR should NOT be preceded by a function name or a closing
|
||||
# bracket (which could be a function which returns a function or a
|
||||
# list/dictionary that contains a function)...
|
||||
# That LPAR should NOT be preceded by a colon (which could be a
|
||||
# dictionary value), function name, or a closing bracket (which
|
||||
# could be a function returning a function or a list/dictionary
|
||||
# containing a function)...
|
||||
if is_valid_index(idx - 2) and (
|
||||
LL[idx - 2].type == token.NAME or LL[idx - 2].type in CLOSING_BRACKETS
|
||||
LL[idx - 2].type == token.COLON
|
||||
or LL[idx - 2].type == token.NAME
|
||||
or LL[idx - 2].type in CLOSING_BRACKETS
|
||||
):
|
||||
continue
|
||||
|
||||
@ -2259,12 +2238,12 @@ def do_transform(
|
||||
elif right_leaves and right_leaves[-1].type == token.RPAR:
|
||||
# Special case for lambda expressions as dict's value, e.g.:
|
||||
# my_dict = {
|
||||
# "key": lambda x: f"formatted: {x},
|
||||
# "key": lambda x: f"formatted: {x}",
|
||||
# }
|
||||
# After wrapping the dict's value with parentheses, the string is
|
||||
# followed by a RPAR but its opening bracket is lambda's, not
|
||||
# the string's:
|
||||
# "key": (lambda x: f"formatted: {x}),
|
||||
# "key": (lambda x: f"formatted: {x}"),
|
||||
opening_bracket = right_leaves[-1].opening_bracket
|
||||
if opening_bracket is not None and opening_bracket in left_leaves:
|
||||
index = left_leaves.index(opening_bracket)
|
||||
|
@ -2,7 +2,7 @@
|
||||
import logging
|
||||
from concurrent.futures import Executor, ProcessPoolExecutor
|
||||
from datetime import datetime, timezone
|
||||
from functools import partial
|
||||
from functools import cache, partial
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
try:
|
||||
@ -85,12 +85,16 @@ def main(bind_host: str, bind_port: int) -> None:
|
||||
web.run_app(app, host=bind_host, port=bind_port, handle_signals=True, print=None)
|
||||
|
||||
|
||||
@cache
|
||||
def executor() -> Executor:
|
||||
return ProcessPoolExecutor()
|
||||
|
||||
|
||||
def make_app() -> web.Application:
|
||||
app = web.Application(
|
||||
middlewares=[cors(allow_headers=(*BLACK_HEADERS, "Content-Type"))]
|
||||
)
|
||||
executor = ProcessPoolExecutor()
|
||||
app.add_routes([web.post("/", partial(handle, executor=executor))])
|
||||
app.add_routes([web.post("/", partial(handle, executor=executor()))])
|
||||
return app
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Awaitable, Callable, Iterable
|
||||
from collections.abc import Awaitable, Callable, Iterable
|
||||
|
||||
from aiohttp.typedefs import Middleware
|
||||
from aiohttp.web_middlewares import middleware
|
||||
|
@ -12,9 +12,9 @@ file_input: (NEWLINE | stmt)* ENDMARKER
|
||||
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
||||
eval_input: testlist NEWLINE* ENDMARKER
|
||||
|
||||
typevar: NAME [':' expr] ['=' expr]
|
||||
paramspec: '**' NAME ['=' expr]
|
||||
typevartuple: '*' NAME ['=' (expr|star_expr)]
|
||||
typevar: NAME [':' test] ['=' test]
|
||||
paramspec: '**' NAME ['=' test]
|
||||
typevartuple: '*' NAME ['=' (test|star_expr)]
|
||||
typeparam: typevar | paramspec | typevartuple
|
||||
typeparams: '[' typeparam (',' typeparam)* [','] ']'
|
||||
|
||||
|
@ -21,13 +21,14 @@
|
||||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
from collections.abc import Iterable, Iterator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from logging import Logger
|
||||
from typing import IO, Any, Iterable, Iterator, Optional, Union, cast
|
||||
from typing import IO, Any, Optional, Union, cast
|
||||
|
||||
from blib2to3.pgen2.grammar import Grammar
|
||||
from blib2to3.pgen2.tokenize import GoodTokenInfo
|
||||
from blib2to3.pgen2.tokenize import TokenInfo
|
||||
from blib2to3.pytree import NL
|
||||
|
||||
# Pgen imports
|
||||
@ -111,7 +112,7 @@ def __init__(self, grammar: Grammar, logger: Optional[Logger] = None) -> None:
|
||||
logger = logging.getLogger(__name__)
|
||||
self.logger = logger
|
||||
|
||||
def parse_tokens(self, tokens: Iterable[GoodTokenInfo], debug: bool = False) -> NL:
|
||||
def parse_tokens(self, tokens: Iterable[TokenInfo], debug: bool = False) -> NL:
|
||||
"""Parse a series of tokens and return the syntax tree."""
|
||||
# XXX Move the prefix computation into a wrapper around tokenize.
|
||||
proxy = TokenProxy(tokens)
|
||||
@ -179,27 +180,17 @@ def parse_tokens(self, tokens: Iterable[GoodTokenInfo], debug: bool = False) ->
|
||||
assert p.rootnode is not None
|
||||
return p.rootnode
|
||||
|
||||
def parse_stream_raw(self, stream: IO[str], debug: bool = False) -> NL:
|
||||
"""Parse a stream and return the syntax tree."""
|
||||
tokens = tokenize.generate_tokens(stream.readline, grammar=self.grammar)
|
||||
return self.parse_tokens(tokens, debug)
|
||||
|
||||
def parse_stream(self, stream: IO[str], debug: bool = False) -> NL:
|
||||
"""Parse a stream and return the syntax tree."""
|
||||
return self.parse_stream_raw(stream, debug)
|
||||
|
||||
def parse_file(
|
||||
self, filename: Path, encoding: Optional[str] = None, debug: bool = False
|
||||
) -> NL:
|
||||
"""Parse a file and return the syntax tree."""
|
||||
with open(filename, encoding=encoding) as stream:
|
||||
return self.parse_stream(stream, debug)
|
||||
text = stream.read()
|
||||
return self.parse_string(text, debug)
|
||||
|
||||
def parse_string(self, text: str, debug: bool = False) -> NL:
|
||||
"""Parse a string and return the syntax tree."""
|
||||
tokens = tokenize.generate_tokens(
|
||||
io.StringIO(text).readline, grammar=self.grammar
|
||||
)
|
||||
tokens = tokenize.tokenize(text, grammar=self.grammar)
|
||||
return self.parse_tokens(tokens, debug)
|
||||
|
||||
def _partially_consume_prefix(self, prefix: str, column: int) -> tuple[str, str]:
|
||||
|
@ -4,7 +4,6 @@
|
||||
"""Safely evaluate Python string literals without using eval()."""
|
||||
|
||||
import re
|
||||
from typing import Match
|
||||
|
||||
simple_escapes: dict[str, str] = {
|
||||
"a": "\a",
|
||||
@ -20,7 +19,7 @@
|
||||
}
|
||||
|
||||
|
||||
def escape(m: Match[str]) -> str:
|
||||
def escape(m: re.Match[str]) -> str:
|
||||
all, tail = m.group(0, 1)
|
||||
assert all.startswith("\\")
|
||||
esc = simple_escapes.get(tail)
|
||||
@ -29,16 +28,16 @@ def escape(m: Match[str]) -> str:
|
||||
if tail.startswith("x"):
|
||||
hexes = tail[1:]
|
||||
if len(hexes) < 2:
|
||||
raise ValueError("invalid hex string escape ('\\%s')" % tail)
|
||||
raise ValueError(f"invalid hex string escape ('\\{tail}')")
|
||||
try:
|
||||
i = int(hexes, 16)
|
||||
except ValueError:
|
||||
raise ValueError("invalid hex string escape ('\\%s')" % tail) from None
|
||||
raise ValueError(f"invalid hex string escape ('\\{tail}')") from None
|
||||
else:
|
||||
try:
|
||||
i = int(tail, 8)
|
||||
except ValueError:
|
||||
raise ValueError("invalid octal string escape ('\\%s')" % tail) from None
|
||||
raise ValueError(f"invalid octal string escape ('\\{tail}')") from None
|
||||
return chr(i)
|
||||
|
||||
|
||||
|
@ -9,8 +9,9 @@
|
||||
how this parsing engine works.
|
||||
|
||||
"""
|
||||
from collections.abc import Callable, Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
||||
|
||||
from blib2to3.pgen2.grammar import Grammar
|
||||
from blib2to3.pytree import NL, Context, Leaf, Node, RawNode, convert
|
||||
@ -88,18 +89,12 @@ def backtrack(self) -> Iterator[None]:
|
||||
self.parser.is_backtracking = is_backtracking
|
||||
|
||||
def add_token(self, tok_type: int, tok_val: str, raw: bool = False) -> None:
|
||||
func: Callable[..., Any]
|
||||
if raw:
|
||||
func = self.parser._addtoken
|
||||
else:
|
||||
func = self.parser.addtoken
|
||||
|
||||
for ilabel in self.ilabels:
|
||||
with self.switch_to(ilabel):
|
||||
args = [tok_type, tok_val, self.context]
|
||||
if raw:
|
||||
args.insert(0, ilabel)
|
||||
func(*args)
|
||||
self.parser._addtoken(ilabel, tok_type, tok_val, self.context)
|
||||
else:
|
||||
self.parser.addtoken(tok_type, tok_val, self.context)
|
||||
|
||||
def determine_route(
|
||||
self, value: Optional[str] = None, force: bool = False
|
||||
|
@ -2,10 +2,11 @@
|
||||
# Licensed to PSF under a Contributor Agreement.
|
||||
|
||||
import os
|
||||
from typing import IO, Any, Iterator, NoReturn, Optional, Sequence, Union
|
||||
from collections.abc import Iterator, Sequence
|
||||
from typing import IO, Any, NoReturn, Optional, Union
|
||||
|
||||
from blib2to3.pgen2 import grammar, token, tokenize
|
||||
from blib2to3.pgen2.tokenize import GoodTokenInfo
|
||||
from blib2to3.pgen2.tokenize import TokenInfo
|
||||
|
||||
Path = Union[str, "os.PathLike[str]"]
|
||||
|
||||
@ -17,7 +18,7 @@ class PgenGrammar(grammar.Grammar):
|
||||
class ParserGenerator:
|
||||
filename: Path
|
||||
stream: IO[str]
|
||||
generator: Iterator[GoodTokenInfo]
|
||||
generator: Iterator[TokenInfo]
|
||||
first: dict[str, Optional[dict[str, int]]]
|
||||
|
||||
def __init__(self, filename: Path, stream: Optional[IO[str]] = None) -> None:
|
||||
@ -26,8 +27,7 @@ def __init__(self, filename: Path, stream: Optional[IO[str]] = None) -> None:
|
||||
stream = open(filename, encoding="utf-8")
|
||||
close_stream = stream.close
|
||||
self.filename = filename
|
||||
self.stream = stream
|
||||
self.generator = tokenize.generate_tokens(stream.readline)
|
||||
self.generator = tokenize.tokenize(stream.read())
|
||||
self.gettoken() # Initialize lookahead
|
||||
self.dfas, self.startsymbol = self.parse()
|
||||
if close_stream is not None:
|
||||
@ -140,7 +140,7 @@ def calcfirst(self, name: str) -> None:
|
||||
if label in self.first:
|
||||
fset = self.first[label]
|
||||
if fset is None:
|
||||
raise ValueError("recursion for rule %r" % name)
|
||||
raise ValueError(f"recursion for rule {name!r}")
|
||||
else:
|
||||
self.calcfirst(label)
|
||||
fset = self.first[label]
|
||||
@ -155,8 +155,8 @@ def calcfirst(self, name: str) -> None:
|
||||
for symbol in itsfirst:
|
||||
if symbol in inverse:
|
||||
raise ValueError(
|
||||
"rule %s is ambiguous; %s is in the first sets of %s as well"
|
||||
" as %s" % (name, symbol, label, inverse[symbol])
|
||||
f"rule {name} is ambiguous; {symbol} is in the first sets of"
|
||||
f" {label} as well as {inverse[symbol]}"
|
||||
)
|
||||
inverse[symbol] = label
|
||||
self.first[name] = totalset
|
||||
@ -237,16 +237,16 @@ def dump_nfa(self, name: str, start: "NFAState", finish: "NFAState") -> None:
|
||||
j = len(todo)
|
||||
todo.append(next)
|
||||
if label is None:
|
||||
print(" -> %d" % j)
|
||||
print(f" -> {j}")
|
||||
else:
|
||||
print(" %s -> %d" % (label, j))
|
||||
print(f" {label} -> {j}")
|
||||
|
||||
def dump_dfa(self, name: str, dfa: Sequence["DFAState"]) -> None:
|
||||
print("Dump of DFA for", name)
|
||||
for i, state in enumerate(dfa):
|
||||
print(" State", i, state.isfinal and "(final)" or "")
|
||||
for label, next in sorted(state.arcs.items()):
|
||||
print(" %s -> %d" % (label, dfa.index(next)))
|
||||
print(f" {label} -> {dfa.index(next)}")
|
||||
|
||||
def simplify_dfa(self, dfa: list["DFAState"]) -> None:
|
||||
# This is not theoretically optimal, but works well enough.
|
||||
@ -330,15 +330,12 @@ def parse_atom(self) -> tuple["NFAState", "NFAState"]:
|
||||
return a, z
|
||||
else:
|
||||
self.raise_error(
|
||||
"expected (...) or NAME or STRING, got %s/%s", self.type, self.value
|
||||
f"expected (...) or NAME or STRING, got {self.type}/{self.value}"
|
||||
)
|
||||
raise AssertionError
|
||||
|
||||
def expect(self, type: int, value: Optional[Any] = None) -> str:
|
||||
if self.type != type or (value is not None and self.value != value):
|
||||
self.raise_error(
|
||||
"expected %s/%s, got %s/%s", type, value, self.type, self.value
|
||||
)
|
||||
self.raise_error(f"expected {type}/{value}, got {self.type}/{self.value}")
|
||||
value = self.value
|
||||
self.gettoken()
|
||||
return value
|
||||
@ -350,13 +347,10 @@ def gettoken(self) -> None:
|
||||
self.type, self.value, self.begin, self.end, self.line = tup
|
||||
# print token.tok_name[self.type], repr(self.value)
|
||||
|
||||
def raise_error(self, msg: str, *args: Any) -> NoReturn:
|
||||
if args:
|
||||
try:
|
||||
msg = msg % args
|
||||
except Exception:
|
||||
msg = " ".join([msg] + list(map(str, args)))
|
||||
raise SyntaxError(msg, (self.filename, self.end[0], self.end[1], self.line))
|
||||
def raise_error(self, msg: str) -> NoReturn:
|
||||
raise SyntaxError(
|
||||
msg, (str(self.filename), self.end[0], self.end[1], self.line)
|
||||
)
|
||||
|
||||
|
||||
class NFAState:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,8 @@
|
||||
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs
|
||||
|
||||
from typing import Any, Iterable, Iterator, Optional, TypeVar, Union
|
||||
from collections.abc import Iterable, Iterator
|
||||
from typing import Any, Optional, TypeVar, Union
|
||||
|
||||
from blib2to3.pgen2.grammar import Grammar
|
||||
|
||||
@ -267,11 +268,7 @@ def __init__(
|
||||
def __repr__(self) -> str:
|
||||
"""Return a canonical string representation."""
|
||||
assert self.type is not None
|
||||
return "{}({}, {!r})".format(
|
||||
self.__class__.__name__,
|
||||
type_repr(self.type),
|
||||
self.children,
|
||||
)
|
||||
return f"{self.__class__.__name__}({type_repr(self.type)}, {self.children!r})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
@ -420,10 +417,9 @@ def __repr__(self) -> str:
|
||||
from .pgen2.token import tok_name
|
||||
|
||||
assert self.type is not None
|
||||
return "{}({}, {!r})".format(
|
||||
self.__class__.__name__,
|
||||
tok_name.get(self.type, self.type),
|
||||
self.value,
|
||||
return (
|
||||
f"{self.__class__.__name__}({tok_name.get(self.type, self.type)},"
|
||||
f" {self.value!r})"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -526,7 +522,7 @@ def __repr__(self) -> str:
|
||||
args = [type_repr(self.type), self.content, self.name]
|
||||
while args and args[-1] is None:
|
||||
del args[-1]
|
||||
return "{}({})".format(self.__class__.__name__, ", ".join(map(repr, args)))
|
||||
return f"{self.__class__.__name__}({', '.join(map(repr, args))})"
|
||||
|
||||
def _submatch(self, node, results=None) -> bool:
|
||||
raise NotImplementedError
|
||||
|
17
tests/data/cases/annotations.py
Normal file
17
tests/data/cases/annotations.py
Normal file
@ -0,0 +1,17 @@
|
||||
# regression test for #1765
|
||||
class Foo:
|
||||
def foo(self):
|
||||
if True:
|
||||
content_ids: Mapping[
|
||||
str, Optional[ContentId]
|
||||
] = self.publisher_content_store.store_config_contents(files)
|
||||
|
||||
# output
|
||||
|
||||
# regression test for #1765
|
||||
class Foo:
|
||||
def foo(self):
|
||||
if True:
|
||||
content_ids: Mapping[str, Optional[ContentId]] = (
|
||||
self.publisher_content_store.store_config_contents(files)
|
||||
)
|
@ -1,4 +1,3 @@
|
||||
# flags: --preview
|
||||
# long variable name
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 0
|
||||
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 1 # with a comment
|
||||
@ -32,7 +31,8 @@
|
||||
raise ValueError(err.format(key))
|
||||
concatenated_strings = "some strings that are " "concatenated implicitly, so if you put them on separate " "lines it will fit"
|
||||
del concatenated_strings, string_variable_name, normal_function_name, normal_name, need_more_to_make_the_line_long_enough
|
||||
|
||||
del ([], name_1, name_2), [(), [], name_4, name_3], name_1[[name_2 for name_1 in name_0]]
|
||||
del (),
|
||||
|
||||
# output
|
||||
|
||||
@ -92,3 +92,9 @@
|
||||
normal_name,
|
||||
need_more_to_make_the_line_long_enough,
|
||||
)
|
||||
del (
|
||||
([], name_1, name_2),
|
||||
[(), [], name_4, name_3],
|
||||
name_1[[name_2 for name_1 in name_0]],
|
||||
)
|
||||
del ((),)
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.8
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.9
|
||||
with \
|
||||
make_context_manager1() as cm1, \
|
||||
make_context_manager2() as cm2, \
|
||||
@ -85,6 +84,31 @@ async def func():
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# don't remove the brackets here, it changes the meaning of the code.
|
||||
with (x, y) as z:
|
||||
pass
|
||||
|
||||
|
||||
# don't remove the brackets here, it changes the meaning of the code.
|
||||
# even though the code will always trigger a runtime error
|
||||
with (name_5, name_4), name_5:
|
||||
pass
|
||||
|
||||
|
||||
def test_tuple_as_contextmanager():
|
||||
from contextlib import nullcontext
|
||||
|
||||
try:
|
||||
with (nullcontext(),nullcontext()),nullcontext():
|
||||
pass
|
||||
except TypeError:
|
||||
# test passed
|
||||
pass
|
||||
else:
|
||||
# this should be a type error
|
||||
assert False
|
||||
|
||||
# output
|
||||
|
||||
|
||||
@ -173,3 +197,28 @@ async def func():
|
||||
some_other_function(argument1, argument2, argument3="some_value"),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# don't remove the brackets here, it changes the meaning of the code.
|
||||
with (x, y) as z:
|
||||
pass
|
||||
|
||||
|
||||
# don't remove the brackets here, it changes the meaning of the code.
|
||||
# even though the code will always trigger a runtime error
|
||||
with (name_5, name_4), name_5:
|
||||
pass
|
||||
|
||||
|
||||
def test_tuple_as_contextmanager():
|
||||
from contextlib import nullcontext
|
||||
|
||||
try:
|
||||
with (nullcontext(), nullcontext()), nullcontext():
|
||||
pass
|
||||
except TypeError:
|
||||
# test passed
|
||||
pass
|
||||
else:
|
||||
# this should be a type error
|
||||
assert False
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.9
|
||||
# This file uses parenthesized context managers introduced in Python 3.9.
|
||||
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --preview
|
||||
"""
|
||||
87 characters ............................................................................
|
||||
"""
|
9
tests/data/cases/fmtskip10.py
Normal file
9
tests/data/cases/fmtskip10.py
Normal file
@ -0,0 +1,9 @@
|
||||
# flags: --preview
|
||||
def foo(): return "mock" # fmt: skip
|
||||
if True: print("yay") # fmt: skip
|
||||
for i in range(10): print(i) # fmt: skip
|
||||
|
||||
j = 1 # fmt: skip
|
||||
while j < 10: j += 1 # fmt: skip
|
||||
|
||||
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
6
tests/data/cases/fmtskip11.py
Normal file
6
tests/data/cases/fmtskip11.py
Normal file
@ -0,0 +1,6 @@
|
||||
def foo():
|
||||
pass
|
||||
|
||||
|
||||
# comment 1 # fmt: skip
|
||||
# comment 2
|
@ -1,4 +1,3 @@
|
||||
# flags: --preview
|
||||
print () # fmt: skip
|
||||
print () # fmt:skip
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --preview
|
||||
x = "\x1F"
|
||||
x = "\\x1B"
|
||||
x = "\\\x1B"
|
67
tests/data/cases/fstring_quotations.py
Normal file
67
tests/data/cases/fstring_quotations.py
Normal file
@ -0,0 +1,67 @@
|
||||
# Regression tests for long f-strings, including examples from issue #3623
|
||||
|
||||
a = (
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
)
|
||||
|
||||
a = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + \
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
|
||||
a = f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"' + \
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
|
||||
a = (
|
||||
f'bbbbbbb"{"b"}"'
|
||||
'aaaaaaaa'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'\"{"b"}\"'
|
||||
)
|
||||
|
||||
a = (
|
||||
r'\"{"b"}\"'
|
||||
)
|
||||
|
||||
# output
|
||||
|
||||
# Regression tests for long f-strings, including examples from issue #3623
|
||||
|
||||
a = (
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
)
|
||||
|
||||
a = (
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
+ f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = (
|
||||
f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
+ f'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"{"b"}"'
|
||||
)
|
||||
|
||||
a = f'bbbbbbb"{"b"}"' "aaaaaaaa"
|
||||
|
||||
a = f'"{"b"}"'
|
||||
|
||||
a = f'"{"b"}"'
|
||||
|
||||
a = r'\"{"b"}\"'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# flags: --preview --minimum-version=3.10
|
||||
# flags: --minimum-version=3.10
|
||||
# normal, short, function definition
|
||||
def foo(a, b) -> tuple[int, float]: ...
|
||||
|
||||
|
307
tests/data/cases/generics_wrapping.py
Normal file
307
tests/data/cases/generics_wrapping.py
Normal file
@ -0,0 +1,307 @@
|
||||
# flags: --minimum-version=3.12
|
||||
def plain[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic[T, B](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic[T, B,](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def both_magic[T, B,](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_multiline[
|
||||
T,
|
||||
B
|
||||
](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_multiline[
|
||||
T,
|
||||
B
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed1[
|
||||
T,
|
||||
B
|
||||
](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def plain_mixed2[T, B](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_mixed1[
|
||||
T,
|
||||
B
|
||||
](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_mixed2[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_mixed2[T, B,](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_mixed2[T, B,](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def something_something_function[
|
||||
T: Model
|
||||
](param: list[int], other_param: type[T], *, some_other_param: bool = True) -> QuerySet[
|
||||
T
|
||||
]:
|
||||
pass
|
||||
|
||||
|
||||
def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTHER_ONE, AND_YET_ANOTHER_ONE: ThisOneHasTyping](a: T, b: T, c: T, d: T, e: T, f: T, g: T, h: T, i: T, j: T, k: T, l: T, m: T, n: T, o: T, p: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def with_random_comments[
|
||||
Z
|
||||
# bye
|
||||
]():
|
||||
return a
|
||||
|
||||
|
||||
def func[
|
||||
T, # comment
|
||||
U # comment
|
||||
,
|
||||
Z: # comment
|
||||
int
|
||||
](): pass
|
||||
|
||||
|
||||
def func[
|
||||
T, # comment but it's long so it doesn't just move to the end of the line
|
||||
U # comment comment comm comm ent ent
|
||||
,
|
||||
Z: # comment ent ent comm comm comment
|
||||
int
|
||||
](): pass
|
||||
|
||||
|
||||
# output
|
||||
def plain[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_multiline[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_multiline[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed1[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed2[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_mixed1[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_mixed2[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_mixed2[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T, b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_mixed2[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def something_something_function[T: Model](
|
||||
param: list[int], other_param: type[T], *, some_other_param: bool = True
|
||||
) -> QuerySet[T]:
|
||||
pass
|
||||
|
||||
|
||||
def func[
|
||||
A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere,
|
||||
LIKE_THIS,
|
||||
AND_THIS,
|
||||
ANOTHER_ONE,
|
||||
AND_YET_ANOTHER_ONE: ThisOneHasTyping,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
c: T,
|
||||
d: T,
|
||||
e: T,
|
||||
f: T,
|
||||
g: T,
|
||||
h: T,
|
||||
i: T,
|
||||
j: T,
|
||||
k: T,
|
||||
l: T,
|
||||
m: T,
|
||||
n: T,
|
||||
o: T,
|
||||
p: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def with_random_comments[
|
||||
Z
|
||||
# bye
|
||||
]():
|
||||
return a
|
||||
|
||||
|
||||
def func[T, U, Z: int](): # comment # comment # comment
|
||||
pass
|
||||
|
||||
|
||||
def func[
|
||||
T, # comment but it's long so it doesn't just move to the end of the line
|
||||
U, # comment comment comm comm ent ent
|
||||
Z: int, # comment ent ent comm comm comment
|
||||
]():
|
||||
pass
|
@ -1,4 +1,3 @@
|
||||
# flags: --preview
|
||||
m2 = None if not isinstance(dist, Normal) else m** 2 + s * 2
|
||||
m3 = None if not isinstance(dist, Normal) else m ** 2 + s * 2
|
||||
m4 = None if not isinstance(dist, Normal) else m**2 + s * 2
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --preview
|
||||
def func(
|
||||
arg1,
|
||||
arg2,
|
@ -1,7 +1,6 @@
|
||||
# flags: --preview
|
||||
"""I am a very helpful module docstring.
|
||||
|
||||
With trailing spaces (only removed with unify_docstring_detection on):
|
||||
With trailing spaces:
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
@ -39,7 +38,7 @@
|
||||
# output
|
||||
"""I am a very helpful module docstring.
|
||||
|
||||
With trailing spaces (only removed with unify_docstring_detection on):
|
||||
With trailing spaces:
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
|
@ -62,5 +62,4 @@ class MultilineDocstringsAsWell:
|
||||
|
||||
|
||||
class SingleQuotedDocstring:
|
||||
|
||||
"I'm a docstring but I don't even get triple quotes."
|
||||
|
@ -1,4 +1,4 @@
|
||||
# flags: --preview --minimum-version=3.10
|
||||
# flags: --minimum-version=3.10
|
||||
match match:
|
||||
case "test" if case != "not very loooooooooooooog condition": # comment
|
||||
pass
|
||||
|
@ -1,4 +1,4 @@
|
||||
# flags: --minimum-version=3.11 --preview
|
||||
# flags: --minimum-version=3.11
|
||||
|
||||
|
||||
def fn(*args: *tuple[*A, B]) -> None:
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.8
|
||||
def positional_only_arg(a, /):
|
||||
pass
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.8
|
||||
(a := 1)
|
||||
(a := a)
|
||||
if (match := pattern.search(data)) is None:
|
||||
|
@ -14,3 +14,8 @@
|
||||
f((a := b + c for c in range(10)), x)
|
||||
f(y=(a := b + c for c in range(10)))
|
||||
f(x, (a := b + c for c in range(10)), y=z, **q)
|
||||
|
||||
|
||||
# Don't remove parens when assignment expr is one of the exprs in a with statement
|
||||
with x, (a := b):
|
||||
pass
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.9
|
||||
# Unparenthesized walruses are now allowed in set literals & set comprehensions
|
||||
# since Python 3.9
|
||||
{x := 1, 2, 3}
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.8
|
||||
if (foo := 0):
|
||||
pass
|
||||
|
||||
|
@ -11,6 +11,14 @@
|
||||
# exactly line length limit + 1, it won't be split like that.
|
||||
xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)] = 1
|
||||
|
||||
# Regression test for #1187
|
||||
print(
|
||||
dict(
|
||||
a=1,
|
||||
b=2 if some_kind_of_data is not None else some_other_kind_of_data, # some explanation of why this is actually necessary
|
||||
c=3,
|
||||
)
|
||||
)
|
||||
|
||||
# output
|
||||
|
||||
@ -36,3 +44,14 @@
|
||||
xxxxxxxxx_yyy_zzzzzzzz[
|
||||
xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)
|
||||
] = 1
|
||||
|
||||
# Regression test for #1187
|
||||
print(
|
||||
dict(
|
||||
a=1,
|
||||
b=(
|
||||
2 if some_kind_of_data is not None else some_other_kind_of_data
|
||||
), # some explanation of why this is actually necessary
|
||||
c=3,
|
||||
)
|
||||
)
|
||||
|
@ -177,7 +177,6 @@ def test_fails_invalid_post_data(
|
||||
MyLovelyCompanyTeamProjectComponent as component, # DRY
|
||||
)
|
||||
|
||||
|
||||
result = 1 # look ma, no comment migration xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
result = 1 # look ma, no comment migration xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
2
tests/data/cases/preview_fstring.py
Normal file
2
tests/data/cases/preview_fstring.py
Normal file
@ -0,0 +1,2 @@
|
||||
# flags: --unstable
|
||||
f"{''=}" f'{""=}'
|
180
tests/data/cases/preview_import_line_collapse.py
Normal file
180
tests/data/cases/preview_import_line_collapse.py
Normal file
@ -0,0 +1,180 @@
|
||||
# flags: --preview
|
||||
from middleman.authentication import validate_oauth_token
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# case 2 comment after import
|
||||
from middleman.authentication import validate_oauth_token
|
||||
#comment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# case 3 comment after import
|
||||
from middleman.authentication import validate_oauth_token
|
||||
# comment
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
from middleman.authentication import validate_oauth_token
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# case 4 try catch with import after import
|
||||
import os
|
||||
import os
|
||||
|
||||
|
||||
|
||||
try:
|
||||
import os
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
import os
|
||||
def func():
|
||||
a = 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# case 5 multiple imports
|
||||
import os
|
||||
import os
|
||||
|
||||
import os
|
||||
import os
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
for i in range(10):
|
||||
print(i)
|
||||
|
||||
|
||||
# case 6 import in function
|
||||
def func():
|
||||
print()
|
||||
import os
|
||||
def func():
|
||||
pass
|
||||
print()
|
||||
|
||||
|
||||
def func():
|
||||
import os
|
||||
a = 1
|
||||
print()
|
||||
|
||||
|
||||
def func():
|
||||
import os
|
||||
|
||||
|
||||
a = 1
|
||||
print()
|
||||
|
||||
|
||||
def func():
|
||||
import os
|
||||
|
||||
|
||||
|
||||
a = 1
|
||||
print()
|
||||
|
||||
# output
|
||||
|
||||
|
||||
from middleman.authentication import validate_oauth_token
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# case 2 comment after import
|
||||
from middleman.authentication import validate_oauth_token
|
||||
|
||||
# comment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# case 3 comment after import
|
||||
from middleman.authentication import validate_oauth_token
|
||||
|
||||
# comment
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
from middleman.authentication import validate_oauth_token
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# case 4 try catch with import after import
|
||||
import os
|
||||
import os
|
||||
|
||||
try:
|
||||
import os
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
import os
|
||||
|
||||
def func():
|
||||
a = 1
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# case 5 multiple imports
|
||||
import os
|
||||
import os
|
||||
|
||||
import os
|
||||
import os
|
||||
|
||||
for i in range(10):
|
||||
print(i)
|
||||
|
||||
|
||||
# case 6 import in function
|
||||
def func():
|
||||
print()
|
||||
import os
|
||||
|
||||
def func():
|
||||
pass
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def func():
|
||||
import os
|
||||
|
||||
a = 1
|
||||
print()
|
||||
|
||||
|
||||
def func():
|
||||
import os
|
||||
|
||||
a = 1
|
||||
print()
|
||||
|
||||
|
||||
def func():
|
||||
import os
|
||||
|
||||
a = 1
|
||||
print()
|
@ -1,4 +1,25 @@
|
||||
# flags: --unstable
|
||||
# flags: --preview
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx"
|
||||
)
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx"
|
||||
),
|
||||
}
|
||||
x = {
|
||||
"foo": bar,
|
||||
"foo": bar,
|
||||
"foo": (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
),
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx"
|
||||
}
|
||||
|
||||
my_dict = {
|
||||
"something_something":
|
||||
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
|
||||
@ -6,23 +27,90 @@
|
||||
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
|
||||
}
|
||||
|
||||
# Function calls as keys
|
||||
tasks = {
|
||||
get_key_name(
|
||||
foo,
|
||||
bar,
|
||||
baz,
|
||||
): src,
|
||||
loop.run_in_executor(): src,
|
||||
loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src,
|
||||
loop.run_in_executor(
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx
|
||||
): src,
|
||||
loop.run_in_executor(): (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
),
|
||||
}
|
||||
|
||||
# Dictionary comprehensions
|
||||
tasks = {
|
||||
key_name: (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
)
|
||||
for src in sources
|
||||
}
|
||||
tasks = {key_name: foobar for src in sources}
|
||||
tasks = {
|
||||
get_key_name(
|
||||
src,
|
||||
): "foo"
|
||||
for src in sources
|
||||
}
|
||||
tasks = {
|
||||
get_key_name(
|
||||
foo,
|
||||
bar,
|
||||
baz,
|
||||
): src
|
||||
for src in sources
|
||||
}
|
||||
tasks = {
|
||||
get_key_name(): (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
)
|
||||
for src in sources
|
||||
}
|
||||
tasks = {get_key_name(): foobar for src in sources}
|
||||
|
||||
|
||||
# Delimiters inside the value
|
||||
def foo():
|
||||
def bar():
|
||||
x = {
|
||||
common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta(
|
||||
days=i
|
||||
),
|
||||
}
|
||||
x = {
|
||||
common.models.DateTimeField: (
|
||||
datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i)
|
||||
),
|
||||
}
|
||||
x = {
|
||||
"foobar": (123 + 456),
|
||||
}
|
||||
x = {
|
||||
"foobar": (123) + 456,
|
||||
}
|
||||
|
||||
|
||||
my_dict = {
|
||||
"a key in my dict": a_very_long_variable * and_a_very_long_function_call() / 100000.0
|
||||
}
|
||||
|
||||
my_dict = {
|
||||
"a key in my dict": a_very_long_variable * and_a_very_long_function_call() * and_another_long_func() / 100000.0
|
||||
}
|
||||
|
||||
my_dict = {
|
||||
"a key in my dict": MyClass.some_attribute.first_call().second_call().third_call(some_args="some value")
|
||||
}
|
||||
|
||||
{
|
||||
'xxxxxx':
|
||||
"xxxxxx":
|
||||
xxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxx(
|
||||
xxxxxxxxxxxxxx={
|
||||
'x':
|
||||
"x":
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=(
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -30,8 +118,8 @@
|
||||
xxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx={
|
||||
'x': x.xx,
|
||||
'x': x.x,
|
||||
"x": x.xx,
|
||||
"x": x.x,
|
||||
}))))
|
||||
}),
|
||||
}
|
||||
@ -58,7 +146,26 @@ def func():
|
||||
|
||||
|
||||
# output
|
||||
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx"
|
||||
)
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx"
|
||||
),
|
||||
}
|
||||
x = {
|
||||
"foo": bar,
|
||||
"foo": bar,
|
||||
"foo": (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
),
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx"
|
||||
}
|
||||
|
||||
my_dict = {
|
||||
"something_something": (
|
||||
@ -68,12 +175,80 @@ def func():
|
||||
),
|
||||
}
|
||||
|
||||
# Function calls as keys
|
||||
tasks = {
|
||||
get_key_name(
|
||||
foo,
|
||||
bar,
|
||||
baz,
|
||||
): src,
|
||||
loop.run_in_executor(): src,
|
||||
loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src,
|
||||
loop.run_in_executor(
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx
|
||||
): src,
|
||||
loop.run_in_executor(): (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
),
|
||||
}
|
||||
|
||||
# Dictionary comprehensions
|
||||
tasks = {
|
||||
key_name: (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
)
|
||||
for src in sources
|
||||
}
|
||||
tasks = {key_name: foobar for src in sources}
|
||||
tasks = {
|
||||
get_key_name(
|
||||
src,
|
||||
): "foo"
|
||||
for src in sources
|
||||
}
|
||||
tasks = {
|
||||
get_key_name(
|
||||
foo,
|
||||
bar,
|
||||
baz,
|
||||
): src
|
||||
for src in sources
|
||||
}
|
||||
tasks = {
|
||||
get_key_name(): (
|
||||
xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx
|
||||
)
|
||||
for src in sources
|
||||
}
|
||||
tasks = {get_key_name(): foobar for src in sources}
|
||||
|
||||
|
||||
# Delimiters inside the value
|
||||
def foo():
|
||||
def bar():
|
||||
x = {
|
||||
common.models.DateTimeField: (
|
||||
datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i)
|
||||
),
|
||||
}
|
||||
x = {
|
||||
common.models.DateTimeField: (
|
||||
datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i)
|
||||
),
|
||||
}
|
||||
x = {
|
||||
"foobar": 123 + 456,
|
||||
}
|
||||
x = {
|
||||
"foobar": (123) + 456,
|
||||
}
|
||||
|
||||
|
||||
my_dict = {
|
||||
"a key in my dict": (
|
||||
a_very_long_variable * and_a_very_long_function_call() / 100000.0
|
||||
)
|
||||
}
|
||||
|
||||
my_dict = {
|
||||
"a key in my dict": (
|
||||
a_very_long_variable
|
||||
@ -82,7 +257,6 @@ def func():
|
||||
/ 100000.0
|
||||
)
|
||||
}
|
||||
|
||||
my_dict = {
|
||||
"a key in my dict": (
|
||||
MyClass.some_attribute.first_call()
|
||||
@ -113,8 +287,8 @@ def func():
|
||||
|
||||
class Random:
|
||||
def func():
|
||||
random_service.status.active_states.inactive = (
|
||||
make_new_top_level_state_from_dict({
|
||||
random_service.status.active_states.inactive = make_new_top_level_state_from_dict(
|
||||
{
|
||||
"topLevelBase": {
|
||||
"secondaryBase": {
|
||||
"timestamp": 1234,
|
||||
@ -125,5 +299,5 @@ def func():
|
||||
),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -279,7 +279,7 @@ def foo():
|
||||
"........................................................................... \\N{LAO KO LA}"
|
||||
)
|
||||
|
||||
msg = lambda x: f"this is a very very very long lambda value {x} that doesn't fit on a single line"
|
||||
msg = lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line"
|
||||
|
||||
dict_with_lambda_values = {
|
||||
"join": lambda j: (
|
||||
@ -329,6 +329,20 @@ def foo():
|
||||
|
||||
log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""")
|
||||
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx"
|
||||
)
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx",
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx"
|
||||
)
|
||||
}
|
||||
|
||||
# output
|
||||
|
||||
|
||||
@ -842,11 +856,9 @@ def foo():
|
||||
" \\N{LAO KO LA}"
|
||||
)
|
||||
|
||||
msg = (
|
||||
lambda x: (
|
||||
f"this is a very very very long lambda value {x} that doesn't fit on a single"
|
||||
" line"
|
||||
)
|
||||
msg = lambda x: (
|
||||
f"this is a very very very very long lambda value {x} that doesn't fit on a"
|
||||
" single line"
|
||||
)
|
||||
|
||||
dict_with_lambda_values = {
|
||||
@ -882,7 +894,7 @@ def foo():
|
||||
|
||||
log.info(
|
||||
"Skipping:"
|
||||
f" {desc['db_id']} {foo('bar',x=123)} {'foo' != 'bar'} {(x := 'abc=')} {pos_share=} {desc['status']} {desc['exposure_max']}"
|
||||
f' {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
|
||||
)
|
||||
|
||||
log.info(
|
||||
@ -902,7 +914,7 @@ def foo():
|
||||
|
||||
log.info(
|
||||
"Skipping:"
|
||||
f" {'a' == 'b' == 'c' == 'd'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}"
|
||||
f' {"a" == "b" == "c" == "d"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}'
|
||||
)
|
||||
|
||||
log.info(
|
||||
@ -926,3 +938,17 @@ def foo():
|
||||
log.info(
|
||||
f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}"""
|
||||
)
|
||||
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx"
|
||||
)
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": (
|
||||
"xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx"
|
||||
),
|
||||
}
|
||||
x = {
|
||||
"xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx"
|
||||
}
|
||||
|
@ -552,6 +552,7 @@ async def foo(self):
|
||||
}
|
||||
|
||||
# Regression test for https://github.com/psf/black/issues/3506.
|
||||
# Regressed again by https://github.com/psf/black/pull/4498
|
||||
s = (
|
||||
"With single quote: ' "
|
||||
f" {my_dict['foo']}"
|
||||
@ -1239,9 +1240,15 @@ async def foo(self):
|
||||
}
|
||||
|
||||
# Regression test for https://github.com/psf/black/issues/3506.
|
||||
s = f"With single quote: ' {my_dict['foo']} With double quote: \" {my_dict['bar']}"
|
||||
# Regressed again by https://github.com/psf/black/pull/4498
|
||||
s = (
|
||||
"With single quote: ' "
|
||||
f" {my_dict['foo']}"
|
||||
' With double quote: " '
|
||||
f' {my_dict["bar"]}'
|
||||
)
|
||||
|
||||
s = (
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting"
|
||||
f" industry:'{my_dict['foo']}'"
|
||||
)
|
||||
f' industry:\'{my_dict["foo"]}\''
|
||||
)
|
@ -0,0 +1,246 @@
|
||||
# flags: --unstable
|
||||
items = [(x for x in [1])]
|
||||
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2"}
|
||||
if some_var == ""
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
"123456890123457890123468901234567890"
|
||||
if some_var == "long strings"
|
||||
else "123467890123467890"
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
and some_var == "long strings"
|
||||
and {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
"123456890123457890123468901234567890"
|
||||
and some_var == "long strings"
|
||||
and "123467890123467890"
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
long_variable_name
|
||||
and even_longer_variable_name
|
||||
and yet_another_very_long_variable_name
|
||||
)
|
||||
]
|
||||
|
||||
# Shouldn't remove trailing commas
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
),
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
and some_var == "long strings"
|
||||
and {"key": "val"}
|
||||
),
|
||||
]
|
||||
items = [
|
||||
(
|
||||
"123456890123457890123468901234567890"
|
||||
and some_var == "long strings"
|
||||
and "123467890123467890"
|
||||
),
|
||||
]
|
||||
items = [
|
||||
(
|
||||
long_variable_name
|
||||
and even_longer_variable_name
|
||||
and yet_another_very_long_variable_name
|
||||
),
|
||||
]
|
||||
|
||||
# Shouldn't add parentheses
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
]
|
||||
items = [{"key1": "val1", "key2": "val2"} if some_var == "" else {"key": "val"}]
|
||||
|
||||
# Shouldn't crash with comments
|
||||
items = [
|
||||
( # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
) # comment
|
||||
]
|
||||
|
||||
items = [ # comment
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
] # comment
|
||||
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"} # comment
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
|
||||
items = [ # comment
|
||||
( # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
) # comment
|
||||
] # comment
|
||||
|
||||
|
||||
# output
|
||||
items = [(x for x in [1])]
|
||||
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
]
|
||||
items = [{"key1": "val1", "key2": "val2"} if some_var == "" else {"key": "val"}]
|
||||
items = [
|
||||
"123456890123457890123468901234567890"
|
||||
if some_var == "long strings"
|
||||
else "123467890123467890"
|
||||
]
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
and some_var == "long strings"
|
||||
and {"key": "val"}
|
||||
]
|
||||
items = [
|
||||
"123456890123457890123468901234567890"
|
||||
and some_var == "long strings"
|
||||
and "123467890123467890"
|
||||
]
|
||||
items = [
|
||||
long_variable_name
|
||||
and even_longer_variable_name
|
||||
and yet_another_very_long_variable_name
|
||||
]
|
||||
|
||||
# Shouldn't remove trailing commas
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
),
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
and some_var == "long strings"
|
||||
and {"key": "val"}
|
||||
),
|
||||
]
|
||||
items = [
|
||||
(
|
||||
"123456890123457890123468901234567890"
|
||||
and some_var == "long strings"
|
||||
and "123467890123467890"
|
||||
),
|
||||
]
|
||||
items = [
|
||||
(
|
||||
long_variable_name
|
||||
and even_longer_variable_name
|
||||
and yet_another_very_long_variable_name
|
||||
),
|
||||
]
|
||||
|
||||
# Shouldn't add parentheses
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
]
|
||||
items = [{"key1": "val1", "key2": "val2"} if some_var == "" else {"key": "val"}]
|
||||
|
||||
# Shouldn't crash with comments
|
||||
items = [ # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
]
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
] # comment
|
||||
|
||||
items = [ # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
]
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
] # comment
|
||||
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"} # comment
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
]
|
||||
|
||||
items = [ # comment # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
]
|
||||
items = [
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
] # comment # comment
|
@ -1,6 +1,3 @@
|
||||
# flags: --minimum-version=3.7
|
||||
|
||||
|
||||
def f():
|
||||
return (i * 2 async for i in arange(42))
|
||||
|
||||
@ -13,6 +10,7 @@ def g():
|
||||
|
||||
|
||||
async def func():
|
||||
await ...
|
||||
if test:
|
||||
out_batched = [
|
||||
i
|
||||
@ -45,6 +43,7 @@ def g():
|
||||
|
||||
|
||||
async def func():
|
||||
await ...
|
||||
if test:
|
||||
out_batched = [
|
||||
i
|
||||
|
@ -1,6 +1,3 @@
|
||||
# flags: --minimum-version=3.8
|
||||
|
||||
|
||||
def starred_return():
|
||||
my_list = ["value2", "value3"]
|
||||
return "value1", *my_list
|
||||
|
@ -1,5 +1,3 @@
|
||||
# flags: --minimum-version=3.9
|
||||
|
||||
@relaxed_decorator[0]
|
||||
def f():
|
||||
...
|
||||
|
157
tests/data/cases/remove_lone_list_item_parens.py
Normal file
157
tests/data/cases/remove_lone_list_item_parens.py
Normal file
@ -0,0 +1,157 @@
|
||||
items = [(123)]
|
||||
items = [(True)]
|
||||
items = [(((((True)))))]
|
||||
items = [(((((True,)))))]
|
||||
items = [((((()))))]
|
||||
items = [(x for x in [1])]
|
||||
items = {(123)}
|
||||
items = {(True)}
|
||||
items = {(((((True)))))}
|
||||
|
||||
# Requires `hug_parens_with_braces_and_square_brackets` unstable style to remove parentheses
|
||||
# around multiline values
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2"}
|
||||
if some_var == ""
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
|
||||
# Comments should not cause crashes
|
||||
items = [
|
||||
( # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
) # comment
|
||||
]
|
||||
|
||||
items = [ # comment
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
] # comment
|
||||
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"} # comment
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
|
||||
items = [ # comment
|
||||
( # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
) # comment
|
||||
] # comment
|
||||
|
||||
|
||||
# output
|
||||
items = [123]
|
||||
items = [True]
|
||||
items = [True]
|
||||
items = [(True,)]
|
||||
items = [()]
|
||||
items = [(x for x in [1])]
|
||||
items = {123}
|
||||
items = {True}
|
||||
items = {True}
|
||||
|
||||
# Requires `hug_parens_with_braces_and_square_brackets` unstable style to remove parentheses
|
||||
# around multiline values
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [{"key1": "val1", "key2": "val2"} if some_var == "" else {"key": "val"}]
|
||||
|
||||
# Comments should not cause crashes
|
||||
items = [
|
||||
( # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
) # comment
|
||||
]
|
||||
|
||||
items = [ # comment
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
] # comment
|
||||
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"} # comment
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
|
||||
items = [ # comment
|
||||
( # comment
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
)
|
||||
]
|
||||
items = [
|
||||
(
|
||||
{"key1": "val1", "key2": "val2", "key3": "val3"}
|
||||
if some_var == "long strings"
|
||||
else {"key": "val"}
|
||||
) # comment
|
||||
] # comment
|
@ -1,4 +1,4 @@
|
||||
# flags: --minimum-version=3.10 --preview --line-length=79
|
||||
# flags: --minimum-version=3.10 --line-length=79
|
||||
|
||||
match 1:
|
||||
case _ if (True):
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --minimum-version=3.9
|
||||
with (open("bla.txt")):
|
||||
pass
|
||||
|
||||
@ -54,6 +53,19 @@
|
||||
with ((((CtxManager1()))) as example1, (((CtxManager2()))) as example2):
|
||||
...
|
||||
|
||||
# regression tests for #3678
|
||||
with (a, *b):
|
||||
pass
|
||||
|
||||
with (a, (b, *c)):
|
||||
pass
|
||||
|
||||
with (a for b in c):
|
||||
pass
|
||||
|
||||
with (a, (b for c in d)):
|
||||
pass
|
||||
|
||||
# output
|
||||
with open("bla.txt"):
|
||||
pass
|
||||
@ -118,3 +130,16 @@
|
||||
|
||||
with CtxManager1() as example1, CtxManager2() as example2:
|
||||
...
|
||||
|
||||
# regression tests for #3678
|
||||
with (a, *b):
|
||||
pass
|
||||
|
||||
with a, (b, *c):
|
||||
pass
|
||||
|
||||
with (a for b in c):
|
||||
pass
|
||||
|
||||
with a, (b for c in d):
|
||||
pass
|
||||
|
163
tests/data/cases/skip_magic_trailing_comma_generic_wrap.py
Normal file
163
tests/data/cases/skip_magic_trailing_comma_generic_wrap.py
Normal file
@ -0,0 +1,163 @@
|
||||
# flags: --minimum-version=3.12 --skip-magic-trailing-comma
|
||||
def plain[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic[T, B](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic[T, B,](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def both_magic[T, B,](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_multiline[
|
||||
T,
|
||||
B
|
||||
](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_multiline[
|
||||
T,
|
||||
B
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_multiline[
|
||||
T,
|
||||
B,
|
||||
](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed1[
|
||||
T,
|
||||
B
|
||||
](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def plain_mixed2[T, B](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_mixed1[
|
||||
T,
|
||||
B
|
||||
](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def arg_magic_mixed2[T, B](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
def type_param_magic_mixed2[T, B,](
|
||||
a: T,
|
||||
b: T
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_mixed1[
|
||||
T,
|
||||
B,
|
||||
](a: T, b: T,) -> T:
|
||||
return a
|
||||
|
||||
def both_magic_mixed2[T, B,](
|
||||
a: T,
|
||||
b: T,
|
||||
) -> T:
|
||||
return a
|
||||
|
||||
|
||||
# output
|
||||
def plain[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_multiline[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_multiline[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_multiline[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_multiline[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed1[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def plain_mixed2[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_mixed1[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def arg_magic_mixed2[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_mixed1[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def type_param_magic_mixed2[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_mixed1[T, B](a: T, b: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
def both_magic_mixed2[T, B](a: T, b: T) -> T:
|
||||
return a
|
@ -20,6 +20,8 @@ def trailing_comma1[T=int,](a: str):
|
||||
def trailing_comma2[T=int](a: str,):
|
||||
pass
|
||||
|
||||
def weird_syntax[T=lambda: 42, **P=lambda: 43, *Ts=lambda: 44](): pass
|
||||
|
||||
# output
|
||||
|
||||
type A[T = int] = float
|
||||
@ -37,25 +39,31 @@ def trailing_comma2[T=int](a: str,):
|
||||
] = something_that_is_long
|
||||
|
||||
|
||||
def simple[
|
||||
T = something_that_is_long
|
||||
](short1: int, short2: str, short3: bytes) -> float:
|
||||
def simple[T = something_that_is_long](
|
||||
short1: int, short2: str, short3: bytes
|
||||
) -> float:
|
||||
pass
|
||||
|
||||
|
||||
def longer[
|
||||
something_that_is_long = something_that_is_long
|
||||
](something_that_is_long: something_that_is_long) -> something_that_is_long:
|
||||
def longer[something_that_is_long = something_that_is_long](
|
||||
something_that_is_long: something_that_is_long,
|
||||
) -> something_that_is_long:
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma1[
|
||||
T = int,
|
||||
](a: str):
|
||||
](
|
||||
a: str,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma2[
|
||||
T = int
|
||||
](a: str,):
|
||||
def trailing_comma2[T = int](
|
||||
a: str,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def weird_syntax[T = lambda: 42, **P = lambda: 43, *Ts = lambda: 44]():
|
||||
pass
|
||||
|
@ -13,6 +13,8 @@ def it_gets_worse[WhatIsTheLongestTypeVarNameYouCanThinkOfEnoughToMakeBlackSplit
|
||||
|
||||
def magic[Trailing, Comma,](): pass
|
||||
|
||||
def weird_syntax[T: lambda: 42, U: a or b](): pass
|
||||
|
||||
# output
|
||||
|
||||
|
||||
@ -56,3 +58,7 @@ def magic[
|
||||
Comma,
|
||||
]():
|
||||
pass
|
||||
|
||||
|
||||
def weird_syntax[T: lambda: 42, U: a or b]():
|
||||
pass
|
||||
|
@ -1,4 +1,3 @@
|
||||
# flags: --preview
|
||||
def long_function_name_goes_here(
|
||||
x: Callable[List[int]]
|
||||
) -> Union[List[int], float, str, bytes, Tuple[int]]:
|
||||
|
@ -1,9 +1,9 @@
|
||||
# flags: --preview
|
||||
# This is testing an issue that is specific to the preview style
|
||||
# This is testing an issue that is specific to the preview style (wrap_long_dict_values_in_parens)
|
||||
{
|
||||
"is_update": (up := commit.hash in update_hashes)
|
||||
}
|
||||
|
||||
# output
|
||||
# This is testing an issue that is specific to the preview style
|
||||
# This is testing an issue that is specific to the preview style (wrap_long_dict_values_in_parens)
|
||||
{"is_update": (up := commit.hash in update_hashes)}
|
||||
|
@ -232,8 +232,6 @@ file_input
|
||||
fstring
|
||||
FSTRING_START
|
||||
"f'"
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
fstring_replacement_field
|
||||
LBRACE
|
||||
'{'
|
||||
@ -242,8 +240,6 @@ file_input
|
||||
RBRACE
|
||||
'}'
|
||||
/fstring_replacement_field
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
fstring_replacement_field
|
||||
LBRACE
|
||||
'{'
|
||||
@ -252,8 +248,6 @@ file_input
|
||||
RBRACE
|
||||
'}'
|
||||
/fstring_replacement_field
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
FSTRING_END
|
||||
"'"
|
||||
/fstring
|
||||
@ -399,8 +393,6 @@ file_input
|
||||
fstring
|
||||
FSTRING_START
|
||||
"f'"
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
fstring_replacement_field
|
||||
LBRACE
|
||||
'{'
|
||||
@ -419,8 +411,6 @@ file_input
|
||||
RBRACE
|
||||
'}'
|
||||
/fstring_replacement_field
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
FSTRING_END
|
||||
"'"
|
||||
/fstring
|
||||
@ -549,8 +539,6 @@ file_input
|
||||
fstring
|
||||
FSTRING_START
|
||||
"f'"
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
fstring_replacement_field
|
||||
LBRACE
|
||||
'{'
|
||||
@ -559,8 +547,6 @@ file_input
|
||||
RBRACE
|
||||
'}'
|
||||
/fstring_replacement_field
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
fstring_replacement_field
|
||||
LBRACE
|
||||
'{'
|
||||
@ -569,8 +555,6 @@ file_input
|
||||
RBRACE
|
||||
'}'
|
||||
/fstring_replacement_field
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
FSTRING_END
|
||||
"'"
|
||||
/fstring
|
||||
@ -660,8 +644,6 @@ file_input
|
||||
RBRACE
|
||||
'}'
|
||||
/fstring_replacement_field
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
FSTRING_END
|
||||
"'"
|
||||
/fstring
|
||||
@ -744,8 +726,6 @@ file_input
|
||||
RBRACE
|
||||
'}'
|
||||
/fstring_replacement_field
|
||||
FSTRING_MIDDLE
|
||||
''
|
||||
FSTRING_END
|
||||
"'"
|
||||
/fstring
|
||||
|
@ -18,7 +18,7 @@
|
||||
import logging
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, FrozenSet, List, Set
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
from _pytest.nodes import Node
|
||||
|
||||
|
||||
ALL_POSSIBLE_OPTIONAL_MARKERS = StashKey[FrozenSet[str]]()
|
||||
ENABLED_OPTIONAL_MARKERS = StashKey[FrozenSet[str]]()
|
||||
ALL_POSSIBLE_OPTIONAL_MARKERS = StashKey[frozenset[str]]()
|
||||
ENABLED_OPTIONAL_MARKERS = StashKey[frozenset[str]]()
|
||||
|
||||
|
||||
def pytest_addoption(parser: "Parser") -> None:
|
||||
@ -69,7 +69,7 @@ def pytest_configure(config: "Config") -> None:
|
||||
"""
|
||||
ot_ini = config.inicfg.get("optional-tests") or []
|
||||
ot_markers = set()
|
||||
ot_run: Set[str] = set()
|
||||
ot_run: set[str] = set()
|
||||
if isinstance(ot_ini, str):
|
||||
ot_ini = ot_ini.strip().split("\n")
|
||||
marker_re = re.compile(r"^\s*(?P<no>no_)?(?P<marker>\w+)(:\s*(?P<description>.*))?")
|
||||
@ -103,7 +103,7 @@ def pytest_configure(config: "Config") -> None:
|
||||
store[ENABLED_OPTIONAL_MARKERS] = frozenset(ot_run)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config: "Config", items: "List[Node]") -> None:
|
||||
def pytest_collection_modifyitems(config: "Config", items: "list[Node]") -> None:
|
||||
store = config._store
|
||||
all_possible_optional_markers = store[ALL_POSSIBLE_OPTIONAL_MARKERS]
|
||||
enabled_optional_markers = store[ENABLED_OPTIONAL_MARKERS]
|
||||
@ -120,7 +120,7 @@ def pytest_collection_modifyitems(config: "Config", items: "List[Node]") -> None
|
||||
|
||||
|
||||
@lru_cache
|
||||
def skip_mark(tests: FrozenSet[str]) -> "MarkDecorator":
|
||||
def skip_mark(tests: frozenset[str]) -> "MarkDecorator":
|
||||
names = ", ".join(sorted(tests))
|
||||
return pytest.mark.skip(reason=f"Marked with disabled optional tests ({names})")
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user